J'ai vu différentes personnes suggérer que Dataframe.explode
est un moyen utile de le faire, mais il en résulte plus de lignes que le cadre de données d'origine, ce qui n'est pas du tout ce que je veux. Je veux simplement faire l'équivalent Dataframe du très simple:
rdd.map(lambda row: row + [row.my_str_col.split('-')])
qui prend quelque chose qui ressemble à:
col1 | my_str_col
-----+-----------
18 | 856-yygrm
201 | 777-psgdg
et le convertit en ceci:
col1 | my_str_col | _col3 | _col4
-----+------------+-------+------
18 | 856-yygrm | 856 | yygrm
201 | 777-psgdg | 777 | psgdg
Je connais pyspark.sql.functions.split()
, mais il en résulte une colonne de tableau imbriquée au lieu de deux colonnes de niveau supérieur comme je le souhaite.
Idéalement, je souhaite que ces nouvelles colonnes soient également nommées.
pyspark.sql.functions.split()
est la bonne approche: vous devez simplement aplatir la colonne ArrayType imbriquée en plusieurs colonnes de niveau supérieur. Dans ce cas, où chaque tableau ne contient que 2 éléments, c'est très simple. Vous utilisez simplement Column.getItem()
pour récupérer chaque partie du tableau sous forme de colonne:
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))
Le résultat sera:
col1 | my_str_col | NAME1 | NAME2
-----+------------+-------+------
18 | 856-yygrm | 856 | yygrm
201 | 777-psgdg | 777 | psgdg
Je ne sais pas comment résoudre ce problème dans un cas général où les tableaux imbriqués ne sont pas de la même taille, rangée à rangée.
Voici une solution au cas général qui ne nécessite pas de connaître la longueur du tableau à l’avance, en utilisant collect
ou en utilisant udf
s. Malheureusement, cela ne fonctionne que pour spark
version 2.1 et ultérieure, car il nécessite la fonction posexplode
.
Supposons que vous ayez le DataFrame suivant:
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|
#+---+----------+
Fractionnez la colonne letters
, puis utilisez posexplode
pour faire éclater le tableau résultant, ainsi que la position dans le tableau. Utilisez ensuite pyspark.sql.functions.expr
pour récupérer l’élément à l’index pos
dans ce tableau.
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|
#+---+------------+---+---+
Nous créons maintenant deux nouvelles colonnes à partir de ce résultat. La première est le nom de notre nouvelle colonne, qui sera une concaténation de letter
et de l'index dans le tableau. La deuxième colonne sera la valeur à l'index correspondant dans le tableau. Nous obtenons ce dernier en exploitant la fonctionnalité de pyspark.sql.functions.expr
qui nous permet utilise les valeurs de colonne en tant que paramètres .
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|
#+---+-------+---+
Maintenant, nous pouvons simplement groupBy
la num
et pivot
le DataFrame. En réunissant tout cela, nous obtenons:
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|
#+---+-------+-------+-------+-------+
J'ai trouvé une solution pour le cas général inégal (ou lorsque vous obtenez les colonnes imbriquées, obtenues avec la fonction .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.*'])
Fondamentalement, il vous suffit de sélectionner toutes les colonnes précédentes + les colonnes imbriquées 'nom_colonne. *' Et vous les obtiendrez sous forme de deux colonnes de niveau supérieur dans ce cas.