Dataframe.explode
がこれを行うのに便利な方法であると提案するさまざまな人々を見てきましたが、元のデータフレームよりも多くの行が生成されます。私は単純に非常に単純なデータフレームに相当することをしたいだけです。
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()
を知っていますが、それは私が望むように2つのトップレベルの列の代わりにネストされた配列の列になります。
理想的には、これらの新しい列にも名前を付けてください。
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
を使用して、結果の配列と配列内の位置を分解します。次に、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|
#+---+------------+---+---+
次に、この結果から2つの新しい列を作成します。最初の列は、新しい列の名前です。これは、letter
と配列内のインデックスを連結したものです。 2番目の列は、配列内の対応するインデックスの値になります。後者を取得するには、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
をDataFrameにできます。それをすべてまとめると、次のようになります。
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|
#+---+-------+-------+-------+-------+
一般的な不均等なケースの解決策を見つけました(または.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。*'を選択するだけで、この場合は2つの最上位列として取得できます。