Ridenominazione dei nomi delle colonne di un DataFrame in Spark Scala


93

Sto cercando di convertire tutte le intestazioni / i nomi delle colonne di un DataFramein Spark-Scala. fin d'ora mi viene in mente il seguente codice che sostituisce solo il nome di una singola colonna.

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

Risposte:


237

Se la struttura è piatta:

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 cosa più semplice che puoi fare è usare il toDFmetodo:

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)

Se vuoi rinominare singole colonne puoi usare sia selectcon alias:

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

che può essere facilmente generalizzato a più colonne:

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

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

oppure withColumnRenamed:

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

che utilizzare con foldLeftper rinominare più colonne:

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

Con le strutture nidificate ( structs) una possibile opzione è rinominare selezionando un'intera struttura:

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)

Tieni presente che potrebbe influire sui nullabilitymetadati. Un'altra possibilità è rinominare tramite casting:

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)

o:

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)

Ciao @ zero323 Quando utilizzo withColumnRenamed ricevo AnalysisException che non può risolvere "CC8. 1 'colonne di input fornite ... Non riesce anche se CC8.1 è disponibile in DataFrame, guida.
unk1102

@ u449355 Non è chiaro per me se questa è una colonna nidificata o una contenente punti. Nell'ultimo caso i backtick dovrebbero funzionare (almeno in alcuni casi fondamentali).
zero323

1
cosa : _*)significa indf.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
Anton Kim

1
Per rispondere alla domanda di Anton Kim: il : _*è il cosiddetto operatore di scala "splat". Fondamentalmente esplode una cosa simile a un array in un elenco non contenuto, che è utile quando si desidera passare l'array a una funzione che accetta un numero arbitrario di argomenti, ma non ha una versione che accetta un file List[]. Se hai familiarità con Perl, è la differenza tra some_function(@my_array) # "splatted"e some_function(\@my_array) # not splatted ... in perl the backslash "\" operator returns a reference to a thing.
Mylo Stone

1
Questa affermazione è davvero oscura per me df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*).. Potresti scomporla per favore? soprattutto la lookup.getOrElse(c,c)parte.
Aetos

18

Per quelli di voi interessati alla versione PySpark (in realtà è lo stesso in Scala - vedere il commento sotto):

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

    merchants_df_renamed.printSchema()

Risultato:

root
| - merchant_id: integer (nullable = true)
| - categoria: string (nullable = true)
| - sottocategoria: string (nullable = true)
| - commerciante: string (nullable = true)


1
Con l'utilizzo toDF()per rinominare le colonne in DataFrame bisogna fare attenzione. Questo metodo funziona molto più lentamente di altri. Ho DataFrame che contiene 100 milioni di record e una semplice query di conteggio richiede circa 3 secondi, mentre la stessa query con il toDF()metodo richiede circa 16 secondi. Ma quando uso il select col AS col_newmetodo per rinominare ottengo di nuovo ~ 3s. Più di 5 volte più veloce! Spark 2.3.2.3
Ihor Konovalenko

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

Nel caso in cui non sia ovvio, questo aggiunge un prefisso e un suffisso a ciascuno dei nomi di colonna correnti. Ciò può essere utile quando si hanno due tabelle con una o più colonne con lo stesso nome e si desidera unirle ma è comunque possibile disambiguare le colonne nella tabella risultante. Sarebbe sicuramente bello se ci fosse un modo simile per farlo in SQL "normale".


piace di sicuro, bello ed elegante
thebluephantom

1

Supponiamo che il dataframe df abbia 3 colonne id1, nome1, prezzo1 e desideri rinominarle in id2, nome2, prezzo2

val list = List("id2", "name2", "price2")
import spark.implicits._
val df2 = df.toDF(list:_*)
df2.columns.foreach(println)

Ho trovato questo approccio utile in molti casi.


0

il join di tabella di traino non rinomina la chiave unita

// method 1: create a new DF
day1 = day1.toDF(day1.columns.map(x => if (x.equals(key)) x else s"${x}_d1"): _*)

// method 2: use withColumnRenamed
for ((x, y) <- day1.columns.filter(!_.equals(key)).map(x => (x, s"${x}_d1"))) {
    day1 = day1.withColumnRenamed(x, y)
}

lavori!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.