【问题标题】:Apply a function to groupBy data with pyspark使用 pyspark 将函数应用于 groupBy 数据
【发布时间】:2017-04-20 08:49:52
【问题描述】:

在对另一列进行分组时,我正在尝试从 csv 获取字数。我的 csv 有三列:id、message 和 user_id。我读了这个,然后拆分消息并存储一个 unigrams 列表:

+-----------------+--------------------+--------------------+
|               id|             message|             user_id|
+-----------------+--------------------+--------------------+
|10100720363468236|[i'm, sad, to, mi...|dceafb541a1b8e894...|
|10100718944611636|[what, does, the,...|dceafb541a1b8e894...|
|10100718890699676|[at, the, oecd, w...|dceafb541a1b8e894...|
+-----------------+--------------------+--------------------+

接下来,给定我的数据框df,我想按user_id 分组,然后获取每个一元组的计数。作为简单的第一遍,我尝试按user_id 分组并获取分组消息字段的长度:

from collections import Counter
from pyspark.sql.types import ArrayType, StringType, IntegerType
from pyspark.sql.functions import udf

df = self.session.read.csv(self.corptable, header=True,
        mode="DROPMALFORMED",)

# split my messages ....
# message is now ArrayType(StringType())

grouped = df.groupBy(df["user_id"])
counter = udf(lambda l: len(l), ArrayType(StringType()))
grouped.agg(counter(df["message"]))
print(grouped.collect())

我收到以下错误:

pyspark.sql.utils.AnalysisException: "expression '`message`' is neither present in the group by, nor is it an aggregate function. Add to group by or wrap in first() (or first_value) if you don't care which value you get.;"

不确定如何解决此错误。一般来说,在对另一列进行分组时,如何将函数应用于一列?我总是必须创建用户定义函数吗?对 Spark 来说非常新。

编辑:这是我解决这个问题的方法,给定一个单独的 Python 文件中的分词器:

group_field = "user_id"
message_field = "message"

context = SparkContext()
session = SparkSession\
        .builder\
        .appName("dlastk")\
        .getOrCreate()

# add tokenizer
context.addPyFile(tokenizer_path)
from tokenizer import Tokenizer
tokenizer = Tokenizer()
spark_tokenizer = udf(tokenizer.tokenize, ArrayType(StringType()))

df = session.read.csv("myFile.csv", header=True,)
df = df[group_field, message_field]

# tokenize the message field
df = df.withColumn(message_field, spark_tokenizer(df[message_field]))

# create ngrams from tokenized messages
n = 1
grouped = df.rdd.map(lambda row: (row[0], Counter([" ".join(x) for x in zip(*[row[1][i:] for i in range(n)])]))).reduceByKey(add)

# flatten the rdd so that each row contains (group_id, ngram, count, relative frequency
flat = grouped.flatMap(lambda row: [[row[0], x,y, y/sum(row[1].values())] for x,y in row[1].items()])

# rdd -> DF
flat = flat.toDF()
flat.write.csv("myNewCSV.csv")

数据如下:

# after read
+--------------------+--------------------+
|             user_id|             message|
+--------------------+--------------------+
|00035fb0dcfbeaa8b...|To the douchebag ...|
|00035fb0dcfbeaa8b...|   T minus 1 week...|
|00035fb0dcfbeaa8b...|Last full day of ...|
+--------------------+--------------------+

# after tokenize
+--------------------+--------------------+
|             user_id|             message|
+--------------------+--------------------+
|00035fb0dcfbeaa8b...|[to, the, doucheb...|
|00035fb0dcfbeaa8b...|[t, minus, 1, wee...|
|00035fb0dcfbeaa8b...|[last, full, day,...|
+--------------------+--------------------+

# grouped: after 1grams extracted and Counters added
[('00035fb0dcfbeaa8bb70ffe24d614d4dcee446b803eb4063dccf14dd2a474611', Counter({'!': 545, '.': 373, 'the': 306, '"': 225, ...

# flat: after calculating sum and relative frequency for each 1gram
[['00035fb0dcfbeaa8bb70ffe24d614d4dcee446b803eb4063dccf14dd2a474611', 'face', 3, 0.000320547066994337], ['00035fb0dcfbeaa8bb70ffe24d614d4dcee446b803eb4063dccf14dd2a474611', 'was', 26, 0.002778074580617587] ....

# after flat RDD to DF
+--------------------+---------+---+--------------------+
|                  _1|       _2| _3|                  _4|
+--------------------+---------+---+--------------------+
|00035fb0dcfbeaa8b...|     face|  3| 3.20547066994337E-4|
|00035fb0dcfbeaa8b...|      was| 26|0.002778074580617587|
|00035fb0dcfbeaa8b...|      how| 22|0.002350678491291...|
+--------------------+---------+---+--------------------+

【问题讨论】:

    标签: apache-spark pyspark


    【解决方案1】:

    试试:

    from  pyspark.sql.functions import *
    
    df.withColumn("word", explode("message")) \
      .groupBy("user_id", "word").count() \
      .groupBy("user_id") \
      .agg(collect_list(struct("word", "count")))
    

    【讨论】:

      【解决方案2】:

      一种自然的方法是将单词分组到一个列表中,然后使用 python 函数Counter() 生成单词计数。对于这两个步骤,我们将使用udf's。首先,将扁平化由多个数组的collect_list() 产生的嵌套列表:

      unpack_udf = udf(
          lambda l: [item for sublist in l for item in sublist]
      )
      

      第二个,生成字数元组,或者在我们的例子中是struct's:

      from pyspark.sql.types import *
      from collections import Counter
      
      # We need to specify the schema of the return object
      schema_count = ArrayType(StructType([
          StructField("word", StringType(), False),
          StructField("count", IntegerType(), False)
      ]))
      
      count_udf = udf(
          lambda s: Counter(s).most_common(), 
          schema_count
      )
      

      把它们放在一起:

      from pyspark.sql.functions import collect_list
      
      (df.groupBy("id")
       .agg(collect_list("message").alias("message"))
       .withColumn("message", unpack_udf("message"))
       .withColumn("message", count_udf("message"))).show(truncate = False)
      +-----------------+------------------------------------------------------+
      |id               |message                                               |
      +-----------------+------------------------------------------------------+
      |10100718890699676|[[oecd,1], [the,1], [with,1], [at,1]]                 |
      |10100720363468236|[[what,3], [me,1], [sad,1], [to,1], [does,1], [the,1]]|
      +-----------------+------------------------------------------------------+
      

      数据:

      df = sc.parallelize([(10100720363468236,["what", "sad", "to", "me"]),
                           (10100720363468236,["what", "what", "does", "the"]),
                           (10100718890699676,["at", "the", "oecd", "with"])]).toDF(["id", "message"])
      

      【讨论】:

      • 不错,好像和我昨晚想出的一样。不知道哪个效率更高?
      • 您可以尝试使用time 包来测量执行时间。在代码块的开头:start_time = time.time(),在结尾:print("Execution time --- %s seconds ---" % (time.time() - start_time))
      猜你喜欢
      • 1970-01-01
      • 2020-05-28
      • 2023-03-24
      • 1970-01-01
      • 2019-06-18
      • 2018-06-19
      • 2019-02-16
      • 2017-03-16
      • 1970-01-01
      相关资源
      最近更新 更多