[英]Split Spark Dataframe string column into multiple columns
我見過很多人建議Dataframe.explode
是一種有用的方法來做到這一點,但它導致比原始數據幀更多的行,這根本不是我想要的。 我只是想做與非常簡單的 Dataframe 等效的:
rdd.map(lambda row: row + [row.my_str_col.split('-')])
這需要看起來像:
col1 | my_str_col
-----+-----------
18 | 856-yygrm
201 | 777-psgdg
並將其轉換為:
col1 | my_str_col | _col3 | _col4
-----+------------+-------+------
18 | 856-yygrm | 856 | yygrm
201 | 777-psgdg | 777 | psgdg
我知道pyspark.sql.functions.split()
,但它導致嵌套數組列而不是像我想要的兩個頂級列。
理想情況下,我還希望對這些新列進行命名。
pyspark.sql.functions.split()
是正確的方法 - 您只需要將嵌套的 ArrayType 列展平為多個頂級列。 在這種情況下,每個數組只包含 2 個項目,這很容易。 您只需使用Column.getItem()
將數組的每個部分作為列本身檢索:
split_col = pyspark.sql.functions.split(df['my_str_col'], '-')
df = df.withColumn('NAME1', split_col.getItem(0))
df = df.withColumn('NAME2', split_col.getItem(1))
結果將是:
col1 | my_str_col | NAME1 | NAME2
-----+------------+-------+------
18 | 856-yygrm | 856 | yygrm
201 | 777-psgdg | 777 | psgdg
我不確定在嵌套數組從行到行的大小不同的一般情況下如何解決這個問題。
這是一般情況的解決方案,不需要提前知道數組的長度,使用collect
或使用udf
s。 不幸的是,這只適用於spark
2.1 及更高版本,因為它需要posexplode
函數。
假設您有以下 DataFrame:
df = spark.createDataFrame(
[
[1, 'A, B, C, D'],
[2, 'E, F, G'],
[3, 'H, I'],
[4, 'J']
]
, ["num", "letters"]
)
df.show()
#+---+----------+
#|num| letters|
#+---+----------+
#| 1|A, B, C, D|
#| 2| E, F, G|
#| 3| H, I|
#| 4| J|
#+---+----------+
拆分letters
列,然后使用posexplode
將結果數組與數組中的位置一起posexplode
。 接下來使用pyspark.sql.functions.expr
獲取該數組中索引pos
處的元素。
import pyspark.sql.functions as f
df.select(
"num",
f.split("letters", ", ").alias("letters"),
f.posexplode(f.split("letters", ", ")).alias("pos", "val")
)\
.show()
#+---+------------+---+---+
#|num| letters|pos|val|
#+---+------------+---+---+
#| 1|[A, B, C, D]| 0| A|
#| 1|[A, B, C, D]| 1| B|
#| 1|[A, B, C, D]| 2| C|
#| 1|[A, B, C, D]| 3| D|
#| 2| [E, F, G]| 0| E|
#| 2| [E, F, G]| 1| F|
#| 2| [E, F, G]| 2| G|
#| 3| [H, I]| 0| H|
#| 3| [H, I]| 1| I|
#| 4| [J]| 0| J|
#+---+------------+---+---+
現在我們根據這個結果創建兩個新列。 第一個是我們新列的名稱,它將是letter
和數組中索引的串聯。 第二列將是數組中相應索引處的值。 我們通過利用pyspark.sql.functions.expr
的功能來獲得后者,該功能允許我們使用列值作為參數。
df.select(
"num",
f.split("letters", ", ").alias("letters"),
f.posexplode(f.split("letters", ", ")).alias("pos", "val")
)\
.drop("val")\
.select(
"num",
f.concat(f.lit("letter"),f.col("pos").cast("string")).alias("name"),
f.expr("letters[pos]").alias("val")
)\
.show()
#+---+-------+---+
#|num| name|val|
#+---+-------+---+
#| 1|letter0| A|
#| 1|letter1| B|
#| 1|letter2| C|
#| 1|letter3| D|
#| 2|letter0| E|
#| 2|letter1| F|
#| 2|letter2| G|
#| 3|letter0| H|
#| 3|letter1| I|
#| 4|letter0| J|
#+---+-------+---+
現在,我們可以只groupBy
的num
和pivot
的數據幀。 把所有這些放在一起,我們得到:
df.select(
"num",
f.split("letters", ", ").alias("letters"),
f.posexplode(f.split("letters", ", ")).alias("pos", "val")
)\
.drop("val")\
.select(
"num",
f.concat(f.lit("letter"),f.col("pos").cast("string")).alias("name"),
f.expr("letters[pos]").alias("val")
)\
.groupBy("num").pivot("name").agg(f.first("val"))\
.show()
#+---+-------+-------+-------+-------+
#|num|letter0|letter1|letter2|letter3|
#+---+-------+-------+-------+-------+
#| 1| A| B| C| D|
#| 3| H| I| null| null|
#| 2| E| F| G| null|
#| 4| J| null| null| null|
#+---+-------+-------+-------+-------+
這是另一種方法,以防您想用分隔符拆分字符串。
import pyspark.sql.functions as f
df = spark.createDataFrame([("1:a:2001",),("2:b:2002",),("3:c:2003",)],["value"])
df.show()
+--------+
| value|
+--------+
|1:a:2001|
|2:b:2002|
|3:c:2003|
+--------+
df_split = df.select(f.split(df.value,":")).rdd.flatMap(
lambda x: x).toDF(schema=["col1","col2","col3"])
df_split.show()
+----+----+----+
|col1|col2|col3|
+----+----+----+
| 1| a|2001|
| 2| b|2002|
| 3| c|2003|
+----+----+----+
我不認為這種來回向 RDD 的轉換會減慢你的速度......也不要擔心最后的模式規范:它是可選的,你可以避免將解決方案推廣到未知列大小的數據。
我找到了針對一般不均勻情況的解決方案(或者當您獲得通過.split()函數獲得的嵌套列時):
import pyspark.sql.functions as f
@f.udf(StructType([StructField(col_3, StringType(), True),
StructField(col_4, StringType(), True)]))
def splitCols(array):
return array[0], ''.join(array[1:len(array)])
df = df.withColumn("name", splitCols(f.split(f.col("my_str_col"), '-')))\
.select(df.columns+['name.*'])
基本上,您只需要選擇所有前面的列+嵌套的'column_name。*',在這種情況下,您將它們作為兩個頂級列。
我理解你的痛苦。 使用 split() 可以工作,但也可能導致中斷。
讓我們拿你的 df 做一點改動:
df = spark.createDataFrame([('1:"a:3":2001',),('2:"b":2002',),('3:"c":2003',)],["value"])
df.show()
+------------+
| value|
+------------+
|1:"a:3":2001|
| 2:"b":2002|
| 3:"c":2003|
+------------+
如果您嘗試如上所述將 split() 應用於此:
df_split = df.select(split(df.value,":")).rdd.flatMap(
lambda x: x).toDF(schema=["col1","col2","col3"]).show()
你會得到
IllegalStateException:輸入行沒有架構所需的預期值數。 需要 4 個字段,同時提供 3 個值。
那么,有沒有更優雅的方法來解決這個問題? 我很高興有人向我指出它。 pyspark.sql.functions.from_csv()是你的朋友。
以我上面的例子df:
from pyspark.sql.functions import from_csv
# Define a column schema to apply with from_csv()
col_schema = ["col1 INTEGER","col2 STRING","col3 INTEGER"]
schema_str = ",".join(col_schema)
# define the separator because it isn't a ','
options = {'sep': ":"}
# create a df from the value column using schema and options
df_csv = df.select(from_csv(df.value, schema_str, options).alias("value_parsed"))
df_csv.show()
+--------------+
| value_parsed|
+--------------+
|[1, a:3, 2001]|
| [2, b, 2002]|
| [3, c, 2003]|
+--------------+
然后我們可以輕松地展平 df 以將值放在列中:
df2 = df_csv.select("value_parsed.*").toDF("col1","col2","col3")
df2.show()
+----+----+----+
|col1|col2|col3|
+----+----+----+
| 1| a:3|2001|
| 2| b|2002|
| 3| c|2003|
+----+----+----+
沒有休息。 數據正確解析。 生活很好。 喝啤酒。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.