web-dev-qa-db-fra.com

Renommer les noms de colonne d'un DataFrame dans Spark Scala

J'essaie de convertir tous les en-têtes/noms de colonnes d'un DataFrame en Spark-Scala. à partir de maintenant, je viens avec le code suivant qui ne remplace qu'un seul nom de colonne.

for( i <- 0 to origCols.length - 1) {
  df.withColumnRenamed(
    df.columns(i), 
    df.columns(i).toLowerCase
  );
}
81
Sam

Si la structure est plate:

val df = Seq((1L, "a", "foo", 3.0)).toDF
df.printSchema
// root
//  |-- _1: long (nullable = false)
//  |-- _2: string (nullable = true)
//  |-- _3: string (nullable = true)
//  |-- _4: double (nullable = false)

la chose la plus simple que vous puissiez faire est d’utiliser la méthode toDF:

val newNames = Seq("id", "x1", "x2", "x3")
val dfRenamed = df.toDF(newNames: _*)

dfRenamed.printSchema
// root
// |-- id: long (nullable = false)
// |-- x1: string (nullable = true)
// |-- x2: string (nullable = true)
// |-- x3: double (nullable = false)

Si vous souhaitez renommer des colonnes individuelles, vous pouvez utiliser soit select avec alias:

df.select($"_1".alias("x1"))

qui peut être facilement généralisé à plusieurs colonnes:

val lookup = Map("_1" -> "foo", "_3" -> "bar")

df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)

ou withColumnRenamed:

df.withColumnRenamed("_1", "x1")

qui utilisent avec foldLeft pour renommer plusieurs colonnes:

lookup.foldLeft(df)((acc, ca) => acc.withColumnRenamed(ca._1, ca._2))

Avec les structures imbriquées (structs), une option possible est de renommer en sélectionnant une structure entière:

val nested = spark.read.json(sc.parallelize(Seq(
    """{"foobar": {"foo": {"bar": {"first": 1.0, "second": 2.0}}}, "id": 1}"""
)))

nested.printSchema
// root
//  |-- foobar: struct (nullable = true)
//  |    |-- foo: struct (nullable = true)
//  |    |    |-- bar: struct (nullable = true)
//  |    |    |    |-- first: double (nullable = true)
//  |    |    |    |-- second: double (nullable = true)
//  |-- id: long (nullable = true)

@transient val foobarRenamed = struct(
  struct(
    struct(
      $"foobar.foo.bar.first".as("x"), $"foobar.foo.bar.first".as("y")
    ).alias("point")
  ).alias("location")
).alias("record")

nested.select(foobarRenamed, $"id").printSchema
// root
//  |-- record: struct (nullable = false)
//  |    |-- location: struct (nullable = false)
//  |    |    |-- point: struct (nullable = false)
//  |    |    |    |-- x: double (nullable = true)
//  |    |    |    |-- y: double (nullable = true)
//  |-- id: long (nullable = true)

Notez que cela peut affecter les métadonnées nullability. Une autre possibilité est de renommer en lançant:

nested.select($"foobar".cast(
  "struct<location:struct<point:struct<x:double,y:double>>>"
).alias("record")).printSchema

// root
//  |-- record: struct (nullable = true)
//  |    |-- location: struct (nullable = true)
//  |    |    |-- point: struct (nullable = true)
//  |    |    |    |-- x: double (nullable = true)
//  |    |    |    |-- y: double (nullable = true)

ou:

import org.Apache.spark.sql.types._

nested.select($"foobar".cast(
  StructType(Seq(
    StructField("location", StructType(Seq(
      StructField("point", StructType(Seq(
        StructField("x", DoubleType), StructField("y", DoubleType)))))))))
).alias("record")).printSchema

// root
//  |-- record: struct (nullable = true)
//  |    |-- location: struct (nullable = true)
//  |    |    |-- point: struct (nullable = true)
//  |    |    |    |-- x: double (nullable = true)
//  |    |    |    |-- y: double (nullable = true)
218
zero323

Pour ceux d'entre vous intéressés par la version de PySpark (en fait, c'est la même chose dans Scala - voir le commentaire ci-dessous):

merchants_df_renamed = merchants_df.toDF(
    'merchant_id', 'category', 'subcategory', 'merchant')

merchants_df_renamed.printSchema()

Résultat:

root
| - merchant_id: entier (nullable = true)
| - category: chaîne (nullable = true)
| - sous-catégorie: chaîne (nullable = true)
| - marchand: chaîne (nullable = true)

15
Tagar
def aliasAllColumns(t: DataFrame, p: String = "", s: String = ""): DataFrame =
{
  t.select( t.columns.map { c => t.col(c).as( p + c + s) } : _* )
}

Dans le cas contraire, cela ajoute un préfixe et un suffixe à chacun des noms de colonnes actuels. Cela peut être utile lorsque vous avez deux tables avec une ou plusieurs colonnes portant le même nom et que vous souhaitez les joindre tout en pouvant toujours distinguer les colonnes de la table résultante. Ce serait bien s'il y avait une façon similaire de faire cela avec du SQL "normal".

4
Mylo Stone