TL;DR:还有比这更好的方法吗?
columns = ['sex', 'class', 'survived'] # for many columns
grouped_crosstab = sdf.groupBy(*columns).count()
for column in columns:
grouped_crosstab = grouped_crosstab.join(
grouped_crosstab.groupBy(column).agg(F.sum('count').alias(f'{column}_total')),
column,
'left')
问题设置
在Pyspark中,您可以在DataFrame
上使用crosstab
方法来获得数据的二维交叉表格。类似地,groupBy
方法可以返回数据的多维“交叉表”,尽管它是高瘦格式的。
举例来说:
columns = ['x', 'y', 'z'] # columns are assumed to be rather low in cardinality, such as categorical values, not continuous values
two_dimensional_crosstab = df.crosstab(columns[0], columns[1]) # only compares 'x' and 'y'
multi_dimensional_view = df.groupBy(*columns).count() # compares 'x', 'y', and 'z'
让我们用一些示例数据来可视化它
import seaborn
df = seaborn.load_dataset('titanic')
sdf = spark.createDataFrame(df) # how to setup a spark context is outside the scope of this question
数据如下所示:
让我们在sex
和class
上创建一个二维交叉表,并使用crosstab
和groupBy
函数来展示这两个方法的比较:
two_d_crosstab = sdf.crosstab('sex', 'class')
grouped_crosstab = sdf.groupBy('sex', 'class').count()
这些框架看起来像这样:
与crosstab
不同,groupBy
方法可以很好地推广到多列,但必须注意表的格式。
列和行合计
为了统计目的,通常需要在交叉表上具有行和列的总和,例如,调查排序。在二维的情况下,人们可以通过这种(公认的复杂)方法获得这样的信息:
index_column = two_d_crosstab.columns[0]
col_list = two_d_crosstab.columns[1:]
two_d_crosstab = two_d_crosstab.withColumn('column_total', sum([F.col(c) for c in col_list]))
transposed_df = two_d_crosstab.pandas_api()\
.set_index(index_column)\
.T.reset_index()\
.rename(columns = {'index':index_column})\
.to_spark()
col_list = transposed_df.columns[1:]
two_d_crosstab = transposed_df.withColumn('row_total', sum([F.col(c) for c in col_list]))
two_d_crosstab
看起来像这样:
多维合计
如何在多维交叉选项卡上进行此计算?
以下是我尝试过的:
sex_tot = grouped_crosstab.groupBy('sex').agg(F.sum('count').alias('sex_total'))
class_tot = grouped_crosstab.groupBy('class').agg(F.sum('count').alias('class_total'))
grouped_crosstab = grouped_crosstab.join(sex_tot, 'sex', 'left').join(class_tot, 'class', 'left')
输出如下所示:
让我们添加survived
作为第三个维度:
columns = ['sex', 'class', 'survived']
grouped_crosstab = sdf.groupBy(*columns).count()
for column in columns:
grouped_crosstab = grouped_crosstab.join(
grouped_crosstab.groupBy(column).agg(F.sum('count').alias(f'{column}_total')),
column,
'left')
它看起来像这样:
注意,输出中有很多重复的信息。随着列数的增加,group by和join操作的数量也会增加,因此这将变得相当笨拙,特别是在具有数百万行的大型 Dataframe 上。
- 是否有更好的(可扩展的)方法?*
1条答案
按热度按时间ekqde3dh1#
我尝试的解决方案(我在写这个问题时发现的)是使用
cube
方法而不是groupBy
。从二维情况开始:生产:
其中,当显示
null
时,计数被视为“不关心此列中的值”。因此,在屏幕截图的第三行,491是针对任何性别的三等舱乘客的计数,第5行产生整个数据框的总数。请注意,这些计数与之前生成的计数一致。推广到多个维度是微不足道的:
此答案的限制
当两列中都有空值时,答案变得不明确。举例来说:
存在具有不同计数的重复条目。可以表示“不关心”状态,也可以表示数据中的合法值。建议将空值重新编码为
cubeing
之前的另一个值(这是一个单词吗?)以避免这种歧义。