【发布时间】:2021-02-11 22:25:43
【问题描述】:
考虑以下玩具 PySpark 数据框:
+----+-----+
|name|value|
+----+-----+
| A| 1|
| A| 3|
| A| 4|
| A| 5|
| A| 9|
| B| 1|
| B| 3|
| B| 6|
| B| 7|
| B| 8|
+----+-----+
对于每一行X,我想确定Y 和Y.name == X.name 在[X.value - 3, X.value + 3] 范围内有多少行Y.value。对于满足这些条件的行Y,我还想计算平均value:
+----+-----+-------------+--------------+
|name|value|n_vals_in_rng|avg_val_in_rng|
+----+-----+-------------+--------------+
| A| 1| 3| 2.6666667| # (1 + 3 + 4) / 3 = 2.6666667
| A| 3| 4| 3.25| # (1 + 3 + 4 + 5) / 4 = 3.25
| A| 4| 4| 3.25| # ...
| A| 5| 3| 4.0|
| A| 9| 1| 9.0|
| B| 1| 2| 2.0|
| B| 3| 3| 3.3333333|
| B| 6| 4| 6.0|
| B| 7| 3| 7.0|
| B| 8| 3| 7.0|
+----+-----+-------------+--------------+
我可以在 PySpark 中有效地做到这一点吗?如果是这样,怎么做?使用 Pandas 来解决这个问题会更好吗?请注意,我的真实数据集在 name 列中有 ~400k 行和 ~8k 不同名称。
以下是我目前的解决方案。它给出了正确的结果,但在大型数据集上需要很长时间(对于具有约 400k 行的数据框需要几个小时)。
首先,我将数据框按name 分组,然后将所有values 收集到一个存储为新列的列表中。
import pyspark.sql.functions as F
import pyspark.sql.types as T
import numpy as np
# df is the data frame defined above
# define a df to be nested
df_to_nest = df.groupBy("name").agg(F.collect_list("value").alias("values"))
# df_to_nest.show():
# +----+---------------+
# |name| values|
# +----+---------------+
# | A|[1, 3, 4, 5, 9]|
# | B|[1, 3, 6, 7, 8]|
# +----+---------------+
然后我将这个聚合数据框 (df_to_nest) 与原始 df 连接起来:
# join with the original data frame
df = df.join(df_to_nest, "name", "left")
# df.show()
# +----+-----+---------------+
# |name|value| values|
# +----+-----+---------------+
# | A| 1|[1, 3, 4, 5, 9]|
# | A| 3|[1, 3, 4, 5, 9]|
# | A| 4|[1, 3, 4, 5, 9]|
# | A| 5|[1, 3, 4, 5, 9]|
# | A| 9|[1, 3, 4, 5, 9]|
# | B| 1|[1, 3, 6, 7, 8]|
# | B| 3|[1, 3, 6, 7, 8]|
# | B| 6|[1, 3, 6, 7, 8]|
# | B| 7|[1, 3, 6, 7, 8]|
# | B| 8|[1, 3, 6, 7, 8]|
# +----+-----+---------------+
最后,我创建一个user-defined function (UDF) 来处理每一行。
# define a UDF to process each row
def process_row(row):
vals_in_range = [x for x in row.values if abs(x-row.value) <= 3]
return (len(vals_in_range),
float(np.mean(vals_in_range)))
input_schema = F.struct([df[x] for x in df.columns])
output_schema = T.StructType([
T.StructField("n_vals_in_rng", T.IntegerType(), nullable=True),
T.StructField("avg_val_in_rng", T.FloatType(), nullable=True),
])
udf = F.udf(process_row, output_schema)
# apply the UDF
df= df.select("name", "value", "values", udf(input_schema).alias("new_cols"))
# unroll the new columns
df= df.select("name", "value", "new_cols.*", "values")
结果:
# df.show():
# +----+-----+-------------+--------------+---------------+
# |name|value|n_vals_in_rng|avg_val_in_rng| values|
# +----+-----+-------------+--------------+---------------+
# | A| 1| 3| 2.6666667|[1, 3, 4, 5, 9]|
# | A| 3| 4| 3.25|[1, 3, 4, 5, 9]|
# | A| 4| 4| 3.25|[1, 3, 4, 5, 9]|
# | A| 5| 3| 4.0|[1, 3, 4, 5, 9]|
# | A| 9| 1| 9.0|[1, 3, 4, 5, 9]|
# | B| 1| 2| 2.0|[1, 3, 6, 7, 8]|
# | B| 3| 3| 3.3333333|[1, 3, 6, 7, 8]|
# | B| 6| 4| 6.0|[1, 3, 6, 7, 8]|
# | B| 7| 3| 7.0|[1, 3, 6, 7, 8]|
# | B| 8| 3| 7.0|[1, 3, 6, 7, 8]|
# +----+-----+-------------+--------------+---------------+
【问题讨论】:
-
你在 spark 中研究过窗口函数吗?我相信这可以通过窗口函数更好地实现。
标签: python pandas dataframe pyspark apache-spark-sql