我需要转换一个DataFrame,其中的一列由一个元组列表组成,每个元组中的每个项目都必须是一个单独的列。
这是熊猫的示例和解决方案:
import pandas as pd
df_dict = {
'a': {
"1": "stuff", "2": "stuff2"
},
"d": {
"1": [(1, 2), (3, 4)], "2": [(1, 2), (3, 4)]
}
}
df = pd.DataFrame.from_dict(df_dict)
print(df) # intial structure
a d
1 stuff [(1, 2), (3, 4)]
2 stuff2 [(1, 2), (3, 4)]
# first transformation, let's separate each list item into a new row
row_breakdown = df.set_index(["a"])["d"].apply(pd.Series).stack()
print(row_breakdown)
a
stuff 0 (1, 2)
1 (3, 4)
stuff2 0 (1, 2)
1 (3, 4)
dtype: object
row_breakdown = row_breakdown.reset_index().drop(columns=["level_1"])
print(row_breakdown)
a 0
0 stuff (1, 2)
1 stuff (3, 4)
2 stuff2 (1, 2)
3 stuff2 (3, 4)
# second transformation, let's get each tuple item into a separate column
row_breakdown.columns = ["a", "d"]
row_breakdown = row_breakdown["d"].apply(pd.Series)
row_breakdown.columns = ["value_1", "value_2"]
print(row_breakdown)
value_1 value_2
0 1 2
1 3 4
2 1 2
3 3 4
这是熊猫解决方案。我需要能够执行相同的操作,但要使用PySpark(2.3)。我已经开始研究它,但立即陷入困境:
from pyspark.context import SparkContext, SparkConf
from pyspark.sql.session import SparkSession
conf = SparkConf().setAppName("appName").setMaster("local")
sc = SparkContext(conf=conf)
spark = SparkSession(sc)
df_dict = {
'a': {
"1": "stuff", "2": "stuff2"
},
"d": {
"1": [(1, 2), (3, 4)], "2": [(1, 2), (3, 4)]
}
}
df = pd.DataFrame(df_dict)
ddf = spark.createDataFrame(df)
row_breakdown = ddf.set_index(["a"])["d"].apply(pd.Series).stack()
AttributeError: 'DataFrame' object has no attribute 'set_index'
显然,Spark不支持索引。任何指针表示赞赏。
答案 0 :(得分:2)
这可能会做到:
from pyspark.context import SparkContext, SparkConf
from pyspark.sql.session import SparkSession
from pyspark.sql import functions as F
import pandas as pd
conf = SparkConf().setAppName("appName").setMaster("local")
sc = SparkContext(conf=conf)
spark = SparkSession(sc)
df_dict = {
'a': {
"1": "stuff", "2": "stuff2"
},
"d": {
"1": [(1, 2), (3, 4)], "2": [(1, 2), (3, 4)]
}
}
df = pd.DataFrame(df_dict)
ddf = spark.createDataFrame(df)
exploded = ddf.withColumn('d', F.explode("d"))
exploded.show()
结果:
+------+------+
| a| d|
+------+------+
| stuff|[1, 2]|
| stuff|[3, 4]|
|stuff2|[1, 2]|
|stuff2|[3, 4]|
+------+------+
为此,我更愿意使用SQL:
exploded.createOrReplaceTempView("exploded")
spark.sql("SELECT a, d._1 as value_1, d._2 as value_2 FROM exploded").show()
重要说明:之所以使用_1
和_2
访问器,是因为spark将元组解析为结构并为其提供了默认键。如果在实际的实现中数据框包含array<int>
,则应使用[0]
语法。
最终结果是:
+------+-------+-------+
| a|value_1|value_2|
+------+-------+-------+
| stuff| 1| 2|
| stuff| 3| 4|
|stuff2| 1| 2|
|stuff2| 3| 4|
+------+-------+-------+
答案 1 :(得分:1)
更新
如果您从具有以下架构的DataFrame开始:
ddf.printSchema()
#root
# |-- a: string (nullable = true)
# |-- d: array (nullable = true)
# | |-- element: struct (containsNull = true)
# | | |-- _1: long (nullable = true)
# | | |-- _2: long (nullable = true)
您必须使用pyspark.sql.functions.explode
将数组分解为列,但之后可以使用*
选择器将结构转换为列:
from pyspark.sql.functions import explode
row_breakdown = ddf.select("a", explode("d").alias("d")).select("a", "d.*")
row_breakdown.show()
#+------+---+---+
#| a| _1| _2|
#+------+---+---+
#| stuff| 1| 2|
#| stuff| 3| 4|
#|stuff2| 1| 2|
#|stuff2| 3| 4|
#+------+---+---+
要重命名列,可以对str.replace
使用列表推导:
from pyspark.sql.functions import col
row_breakdown = row_breakdown.select(
*[col(c).alias(c.replace("_", "value")) for c in row_breakdown.columns]
)
row_breakdown.show()
#+------+------+------+
#| a|value1|value2|
#+------+------+------+
#| stuff| 1| 2|
#| stuff| 3| 4|
#|stuff2| 1| 2|
#|stuff2| 3| 4|
#+------+------+------+
原始答案
如果您是从字典开始的,则完全不需要使用pandas
。
相反,您可以直接从字典中创建DataFrame。关键是到transform your dictionary into the appropriate format,然后使用它来构建您的Spark DataFrame。
在您的示例中,您似乎根本没有使用a
键下的值。
像我mentioned in my comment一样,您可以使用以下代码实现上述输出:
df_dict = {
'a': {
"1": "stuff", "2": "stuff2"
},
"d": {
"1": [(1, 2), (3, 4)], "2": [(1, 2), (3, 4)]
}
}
from itertools import chain
row_breakdown = spark.createDataFrame(
chain.from_iterable(df_dict["d"].values()), ["value1", "value2"]
)
row_breakdown.show()
#+------+------+
#|value1|value2|
#+------+------+
#| 1| 2|
#| 3| 4|
#| 1| 2|
#| 3| 4|
#+------+------+
如果要使用类似索引的列,则可以通过使用enumerate
来实现,如以下示例所示。在这里,我还按键对值进行排序,这似乎是您的意图。
data = (
(i,) + v for i, v in enumerate(
chain.from_iterable(
v for k, v in sorted(df_dict["d"].items(), key=lambda (key, val): key)
)
)
)
columns = ["index", "value1", "value2"]
row_breakdown = spark.createDataFrame(data, columns)
row_breakdown.show()
#+-----+------+------+
#|index|value1|value2|
#+-----+------+------+
#| 0| 1| 2|
#| 1| 3| 4|
#| 2| 1| 2|
#| 3| 3| 4|
#+-----+------+------+
正如您在此处看到的,我们可以将生成器表达式传递给spark.createDataFrame
,并且此解决方案不需要我们提前知道元组的长度。