如何在任意列上转动?

时间:2017-12-08 19:17:34

标签: sql scala apache-spark apache-spark-sql

我使用Apache Spark 2.2.0和Scala。

我正在关注the question作为在不使用数据透视功能的情况下转动数据框的指南。

我需要在不使用pivot函数的情况下转动数据框,因为我有非数字数据,pivot适用于summinmax等聚合函数仅限数值数据。我有一个非数字列,我想在pivot聚合中使用。

这是我的数据:

+---+-------------+----------+-------------+----------+-------+
|Qid|     Question|AnswerText|ParticipantID|Assessment| GeoTag|
+---+-------------+----------+-------------+----------+-------+
|  1|Question1Text|       Yes|       abcde1|         0|(x1,y1)|
|  2|Question2Text|        No|       abcde1|         0|(x1,y1)|
|  3|Question3Text|         3|       abcde1|         0|(x1,y1)|
|  1|Question1Text|        No|       abcde2|         0|(x2,y2)|
|  2|Question2Text|       Yes|       abcde2|         0|(x2,y2)|
+---+-------------+----------+-------------+----------+-------+

我希望它按ParticipantIDAssessmentGeoTag标记以及Question列上的“数据透视”进行分组,并从AnswerText列中获取值。最后,输出应如下所示:

+-------------+-----------+----------+-------+-----+----- +
|ParticipantID|Assessment |GeoTag    |Qid_1  |Qid_2|Qid_3 |
+-------------+-----------+----------+-------+-----+------+
|abcde1       |0          |(x1,y1)   |Yes    |No   |3     |
|abcde2       |0          |(x2,y2)   |No     |Yes  |null  |
+-------------+-----------+----------+-------+-----+------+

我试过这个:

val questions: Array[String] = df.select("Q_id")
      .distinct()
      .collect()
      .map(_.getAs[String]("Q_id"))
      .sortWith(_<_)

val df2: DataFrame = questions.foldLeft(df) {
      case (data, question) => data.selectExpr("*", s"IF(Q_id = '$question', AnswerText, 0) AS $question")
    }

[后跟GroupBy表达式]

但是我收到以下错误,这必须与最终语句AS $question

的语法有关
17/12/08 16:13:12 INFO SparkSqlParser: Parsing command: *
17/12/08 16:13:12 INFO SparkSqlParser: Parsing command: IF(Q_id_string_new_2 = '101_Who_is_with_you_right_now?', AnswerText, 0) AS 101_Who_is_with_you_right_now?


extraneous input '?' expecting <EOF>(line 1, pos 104)

== SQL ==
IF(Q_id_string_new_2 = '101_Who_is_with_you_right_now?', AnswerText, 0) AS 101_Who_is_with_you_right_now?
--------------------------------------------------------------------------------------------------------^^^

org.apache.spark.sql.catalyst.parser.ParseException: 
extraneous input '?' expecting <EOF>(line 1, pos 104)

== SQL ==
IF(Q_id_string_new_2 = '101_Who_is_with_you_right_now?', AnswerText, 0) AS 101_Who_is_with_you_right_now?
--------------------------------------------------------------------------------------------------------^^^

    at org.apache.spark.sql.catalyst.parser.ParseException.withCommand(ParseDriver.scala:217)

我出错的任何想法?有没有更好的办法?如果有必要,我考虑过在Spark之外恢复到Pandas和Python,但如果可能的话,我宁愿在同一个框架中编写所有代码。

2 个答案:

答案 0 :(得分:3)

$question将问题变量的值替换为SQL语句时,最终得到的列名称为“?”在SQL中。 ?不是列名中的有效字符,因此您必须至少使用反引号来引用:

s"IF(Q_id = '$question', AnswerText, 0) AS `$question`"

或使用select / withColumn

import org.apache.spark.sql.functions.when

case (data, question) => 
  data.withColumn(question, when($"Q_id" === question, $"AnswerText"))

首先使用regexp_replace对字符串进行消息处理。

  

需要在不使用pivot函数的情况下转动数据帧,因为我有非数值数据,而df.pivot只适用于数值数据上的sum,min,max等聚合函数。

您可以使用firstHow to use pivot and calculate average on a non-numeric column (facing AnalysisException "is not a numeric column")?

data.groupBy($"ParticipantID", $"Assessment", $"GeoTag")
  .pivot($"Question", questions).agg(first($"AnswerText"))

答案 1 :(得分:0)

只需注意accepted answer by @user8371915,即可使查询速度更快。


有一种方法可以避免昂贵的扫描生成带有标头的questions

只需生成标题(在相同的工作和阶段中!),然后在列上生成pivot

// It's a simple and cheap map-like transformation
val qid_header = input.withColumn("header", concat(lit("Qid_"), $"Qid"))
scala> qid_header.show
+---+-------------+----------+-------------+----------+-------+------+
|Qid|     Question|AnswerText|ParticipantID|Assessment| GeoTag|header|
+---+-------------+----------+-------------+----------+-------+------+
|  1|Question1Text|       Yes|       abcde1|         0|(x1,y1)| Qid_1|
|  2|Question2Text|        No|       abcde1|         0|(x1,y1)| Qid_2|
|  3|Question3Text|         3|       abcde1|         0|(x1,y1)| Qid_3|
|  1|Question1Text|        No|       abcde2|         0|(x2,y2)| Qid_1|
|  2|Question2Text|       Yes|       abcde2|         0|(x2,y2)| Qid_2|
+---+-------------+----------+-------------+----------+-------+------+

将标头作为数据集的一部分,让我们进行透视。

val solution = qid_header
  .groupBy('ParticipantID, 'Assessment, 'GeoTag)
  .pivot('header)
  .agg(first('AnswerText))
scala> solution.show
+-------------+----------+-------+-----+-----+-----+
|ParticipantID|Assessment| GeoTag|Qid_1|Qid_2|Qid_3|
+-------------+----------+-------+-----+-----+-----+
|       abcde1|         0|(x1,y1)|  Yes|   No|    3|
|       abcde2|         0|(x2,y2)|   No|  Yes| null|
+-------------+----------+-------+-----+-----+-----+