web-dev-qa-db-ja.com

Spark Dataframe string列を複数の列に分割します

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つのトップレベルの列の代わりにネストされた配列の列になります。

理想的には、これらの新しい列にも名前を付けてください。

42
Peter Gaultney

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

ネストされた配列が行ごとに同じサイズではない一般的なケースでこれをどのように解決するかはわかりません。

73
Peter Gaultney

以下は、collectを使用して、またはudfsを使用して、事前に配列の長さを知る必要がない一般的な場合の解決策です。残念ながら、これは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|
#+---+-------+---+

これで、groupBynumに、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|
#+---+-------+-------+-------+-------+
23
pault

一般的な不均等なケースの解決策を見つけました(または.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つの最上位列として取得できます。

0
Jasminyas