NA 值的干净替代 pandas 的损坏交叉表

Posted

技术标签:

【中文标题】NA 值的干净替代 pandas 的损坏交叉表【英文标题】:Clean alternative to pandas' broken crosstab for NA values 【发布时间】:2022-01-08 20:26:05 【问题描述】:

我正在尝试获得一个与 R 的表函数类似的函数,参数useNA 允许我在交叉表中包含 NA 值。

这是一个小例子:

df = pd.DataFrame("a": [0, 1, pd.NA, pd.NA], "b":[2, pd.NA, 3, pd.NA])
print(pd.crosstab(df["a"], df["b"], dropna=False)

我从中得到的是

b  2  3
a      
0  1  0

但我希望它是这样的

b   2  3  NA
a      
0   1  0  0
1   0  0  1
NA  0  1  1

这不仅忽略了数据框中的四分之三行,结果还取决于插入两个系列的顺序,这里是pd.crosstab(df["b"], df["a"], dropna=False)

a  0  1
b      
2  1  0

我能想到的一种解决方法是查看两个系列中的唯一值并创建一个新值,它们都不在它们中,并使用 fillna 将其临时替换为 NA 值,但这感觉非常拙劣,如果还没有完全符合我要求的东西,我会感到惊讶。

另外,在两个系列之一没有丢失数据的情况下,该解决方案无法按预期工作。

编辑:添加示例来说明最后一部分。

df = pd.DataFrame("a": [0, 1, 2, 3], "b":[2, pd.NA, 3, pd.NA])
print(pd.crosstab(df["a"].fillna("NA"), df["b"].fillna("NA"), dropna=False)

输出:

b  2  3  NA
a          
0  1  0   0
1  0  0   1
2  0  1   0
3  0  0   1

预期:

b   2  3  NA
a          
0   1  0   0
1   0  0   1
2   0  1   0
3   0  0   1
NA  0  0   0

【问题讨论】:

【参考方案1】:

您可以将缺失值替换为NA

print(pd.crosstab(t["a"].fillna('NA'), t["b"].fillna('NA')))
b   2  3  NA
a           
0   1  0   0
1   0  0   1
NA  0  1   1

编辑:添加由NA 填充的新行,然后从交集NA, NA 中减去1

t = pd.DataFrame("a": [0, 1, 2, 3], "b":[2, pd.NA, 3, pd.NA])

df = t.append(pd.DataFrame('NA', index=[-1], columns=t.columns)).fillna('NA')
df = pd.crosstab(df["a"], df["b"])
df.loc['NA','NA'] -= 1
print(df)
b   2  3  NA
a           
0   1  0   0
1   0  0   1
2   0  1   0
3   0  0   1
NA  0  0   0

groupby.size + stack 可以使用:

t = pd.DataFrame("a": [0, 1, 2, 3], "b":[2, pd.NA, 3, pd.NA])

df = t.append(pd.DataFrame(np.nan, index=[-1], columns=t.columns))
df = df.groupby(['a', 'b'], dropna = False).size().unstack(fill_value=0)
df.loc[np.nan,np.nan] -= 1
print(df)
b    2.0  3.0  NaN
a                 
0.0    1    0    0
1.0    0    0    1
2.0    0    1    0
3.0    0    0    1
NaN    0    0    0

【讨论】:

这是我在帖子最后部分的想法和提到的,但如果一列没有缺失数据,NA 将不会显示在该列的结果中。我可以编辑原始帖子以通过另一个示例来反映这一点。 @Uretki - 答案已编辑。【参考方案2】:

crosstab 是一个方便的选项,包裹在 pd.pivot_table 周围;您可以直接使用 groupby(pd.pivot_table 是 groupby 的包装)并复制您的输出:

df.groupby(['a', 'b'], dropna = False).size().unstack(fill_value=0)

b    2.0  3.0  NaN
a                 
0.0    1    0    0
1.0    0    0    1
NaN    0    1    1

如果您可以对 pandas 进行 PR 以改进交叉表功能,这也会有所帮助

【讨论】:

这看起来比使用 fillna 更干净,但它仍然缺少一个系列没有丢失数据并且因此没有 NA 值的情况。我猜这两个答案仍然回答了问题中最重要的部分,所以如果没有任何问题/我想不出解决方案,我会接受它。 忘了补充,关于 PR,我不能假装我能想出一个干净的优化解决方案并提交它。如果您的意思是报告问题,它已经完成了。 你是对的,因为一个系列没有NA,所以不包括在内;不确定如何处理它;对于公关来说,优化没关系……我相信小步骤……我们都是学习者 您可能会再次重新索引;但在这一点上,我认为它开始变得粗糙

以上是关于NA 值的干净替代 pandas 的损坏交叉表的主要内容,如果未能解决你的问题,请参考以下文章

Bigquery - 交叉连接的替代方案

Pandas:根据现有值的分布填充要填充的NA值

Pandas 滚动回归:循环的替代方案

Pandas 重采样的替代方案

在 Python 中合并交叉表

速度起飞!替代 pandas 的 8 个神库