带有无序分类变量的 Pandas 交叉表

问题描述

对于一个只是为了好玩的项目,我生成一个包含所有当前存在的 Pokemon(916,不包括 Megas 或替代形式)的数据集,并收集了有关基本统计数据、能力和类型的数据。现在我想生成一个交叉表,显示每个类型组合的分布。当前对数据进行编码,使得第一种和第二种类型是单独的变量,Type1Type2。这种格式适用于 pd.crosstab(),假设类型的顺序不同,('Flying','normal')('normal','Flying') 不同;然而,游戏并没有没有做出这样的区分。我想生成反映这一点的频率表 - 基本上沿对角线将 pd.crosstab() 表折叠成两半。

#### For data structured like...
In[1]: dfNatDex[dfNatDex['Dexnum']<10]
Out[4]: 
    Dexnum        Name  Type1   Type2
0      1.0   Bulbasaur  grass  poison
1      2.0     Ivysaur  grass  poison
2      3.0    Venusaur  grass  poison
3      4.0  Charmander   fire    fire
4      5.0  Charmeleon   fire    fire
5      6.0   Charizard   fire  flying
6      7.0    Squirtle  water   water
7      8.0   Wartortle  water   water
8      9.0   Blastoise  water   water

[10 rows x 16 columns]

#### I am getting...
In[2]: crosstab(dfNatDex['Type2'][...],dfNatDex['Type1'][...])
Out[2]: 
Type1   flying  normal  water
Type2                        
flying       3      26      7
normal       0      69      0
water        1       1     67

#### I want to get...
Type1   flying  normal  water
Type2                        
flying       3      26      8
normal       .      69      1
water        .       .     67

我的猜测是,如果我还没有找到用于此的 Pandas 函数,那么也许我可以通过矩阵运算来实现这一点。如果做不到这一点,我认为可能会有一个缓慢的迭代过程来实现这一目标。

解决方法

一种选择是使用 np.sort 对轴 = 1 上的值进行排序,然后使用 value_counts 获取计数:

import numpy as np
import pandas as pd

cols = ['Type1','Type2']
types_df = pd.DataFrame(
    np.sort(df[cols],axis=1),columns=cols
).value_counts().reset_index(name='Count')

types_df

   Type1   Type2  Count
0  grass  poison      3
1  water   water      3
2   fire    fire      2
3   fire  flying      1

crosstab 也可以用于已排序的值,但是会有很多 0 值可能会掩盖试图展示的信息:

cols = ['Type1','Type2']
types_df = pd.DataFrame(np.sort(df[cols],columns=cols)
ct_df = pd.crosstab(types_df['Type2'],types_df['Type1'])

ct_df

Type1   fire  grass  water
Type2                     
fire       2      0      0
flying     1      0      0
poison     0      3      0
water      0      0      3

假设这些类型:

df[['Type1','Type2']]
    Type1   Type2
0  poison   grass  # poison grass
1   grass  poison  # grass poison
2   grass  poison
3    fire    fire
4    fire    fire
5    fire  flying
6   water   water
7   water   water
8   water   water

排序后:

np.sort(df[['Type1','Type2']],axis=1)
[['grass' 'poison']  # grass poison
 ['grass' 'poison']  # grass poison
 ['grass' 'poison']
 ['fire' 'fire']
 ['fire' 'fire']
 ['fire' 'flying']
 ['water' 'water']
 ['water' 'water']
 ['water' 'water']]

这样,所有类型都以相同的顺序出现,无论它们在 DataFrame 中如何出现,并且无论它们在列中出现的顺序如何,值计数都会产生正确的值。