J'ai un Dataframe que j'essaye d'aplatir. Dans le cadre du processus, je veux l'exploser, donc si j'ai une colonne de tableaux, chaque valeur du tableau sera utilisée pour créer une ligne distincte. Par exemple,
id | name | likes
_______________________________
1 | Luke | [baseball, soccer]
devrait devenir
id | name | likes
_______________________________
1 | Luke | baseball
1 | Luke | soccer
C'est mon code
private DataFrame explodeDataFrame(DataFrame df) {
DataFrame resultDf = df;
for (StructField field : df.schema().fields()) {
if (field.dataType() instanceof ArrayType) {
resultDf = resultDf.withColumn(field.name(), org.Apache.spark.sql.functions.explode(resultDf.col(field.name())));
resultDf.show();
}
}
return resultDf;
}
Le problème est que dans mes données, certaines des colonnes du tableau ont des valeurs nulles. Dans ce cas, la ligne entière est supprimée. Donc, cette trame de données:
id | name | likes
_______________________________
1 | Luke | [baseball, soccer]
2 | Lucy | null
devient
id | name | likes
_______________________________
1 | Luke | baseball
1 | Luke | soccer
au lieu de
id | name | likes
_______________________________
1 | Luke | baseball
1 | Luke | soccer
2 | Lucy | null
Comment puis-je faire exploser mes tableaux pour ne pas perdre les lignes nulles?
J'utilise Spark 1.5.2 et Java 8
Spark 2.2 +
Vous pouvez utiliser la fonction explode_outer
:
import org.Apache.spark.sql.functions.explode_outer
df.withColumn("likes", explode_outer($"likes")).show
// +---+----+--------+
// | id|name| likes|
// +---+----+--------+
// | 1|Luke|baseball|
// | 1|Luke| soccer|
// | 2|Lucy| null|
// +---+----+--------+
Spark <= 2.1
Dans Scala mais Java doit être presque identique (pour importer des fonctions individuelles, utilisez import static
)).
import org.Apache.spark.sql.functions.{array, col, explode, lit, when}
val df = Seq(
(1, "Luke", Some(Array("baseball", "soccer"))),
(2, "Lucy", None)
).toDF("id", "name", "likes")
df.withColumn("likes", explode(
when(col("likes").isNotNull, col("likes"))
// If null explode an array<string> with a single null
.otherwise(array(lit(null).cast("string")))))
L'idée ici est essentiellement de remplacer NULL
par une array(NULL)
du type souhaité. Pour les types complexes (a.k.a structs
), vous devez fournir un schéma complet:
val dfStruct = Seq((1L, Some(Array((1, "a")))), (2L, None)).toDF("x", "y")
val st = StructType(Seq(
StructField("_1", IntegerType, false), StructField("_2", StringType, true)
))
dfStruct.withColumn("y", explode(
when(col("y").isNotNull, col("y"))
.otherwise(array(lit(null).cast(st)))))
ou
dfStruct.withColumn("y", explode(
when(col("y").isNotNull, col("y"))
.otherwise(array(lit(null).cast("struct<_1:int,_2:string>")))))
Remarque:
Si le tableau Column
a été créé avec containsNull
défini sur false
, vous devez d'abord le modifier (testé avec Spark 2.1):
df.withColumn("array_column", $"array_column".cast(ArrayType(SomeType, true)))
Suite à la réponse acceptée, lorsque les éléments du tableau sont de type complexe, il peut être difficile de le définir à la main (par exemple avec de grandes structures).
Pour le faire automatiquement, j'ai écrit la méthode d'assistance suivante:
def explodeOuter(df: Dataset[Row], columnsToExplode: List[String]) = {
val arrayFields = df.schema.fields
.map(field => field.name -> field.dataType)
.collect { case (name: String, type: ArrayType) => (name, type.asInstanceOf[ArrayType])}
.toMap
columnsToExplode.foldLeft(df) { (dataFrame, arrayCol) =>
dataFrame.withColumn(arrayCol, explode(when(size(col(arrayCol)) =!= 0, col(arrayCol))
.otherwise(array(lit(null).cast(arrayFields(arrayCol).elementType)))))
}
Vous pouvez utiliser la fonction explode_outer()
.