Renomeando os nomes das colunas de um DataFrame no Spark Scala

93

Estou tentando converter todos os nomes de cabeçalhos / colunas de um DataFrameno Spark-Scala. a partir de agora eu vim com o seguinte código que substitui apenas um único nome de coluna.

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

Respostas:

237

Se a estrutura for plana:

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)

a coisa mais simples que você pode fazer é usar o toDFmétodo:

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 você deseja renomear colunas individuais que você pode usar selectcom alias:

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

que pode ser facilmente generalizado para várias colunas:

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")

que usa com foldLeftpara renomear várias colunas:

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

Com estruturas aninhadas ( structs), uma opção possível é renomear selecionando uma estrutura inteira:

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)

Observe que isso pode afetar os nullabilitymetadados. Outra possibilidade é renomear lançando:

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)
zero323
fonte
Oi @ zero323 Ao usar withColumnRenamed estou recebendo AnalysisException, não é possível resolver 'CC8. 1 'colunas de entrada fornecidas ... Ele falha mesmo que CC8.1 esteja disponível no DataFrame, por favor, guie.
unk1102
@ u449355 Não está claro para mim se esta é uma coluna aninhada ou se contém pontos. No último caso, crases devem funcionar (pelo menos em alguns casos básicos).
zero323
1
o que : _*)significa emdf.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
Anton Kim
1
Para responder à pergunta de Anton Kim: : _*é o chamado operador "splat" da escala. Basicamente, ele explode algo parecido com um array em uma lista não contida, o que é útil quando você deseja passar o array para uma função que recebe um número arbitrário de args, mas não tem uma versão que leva a List[]. Se você está familiarizado com Perl, essa é a diferença entre 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
Esta afirmação é realmente obscura para mim df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*).. Você poderia decompor, por favor? especialmente a lookup.getOrElse(c,c)parte.
Aetos
18

Para aqueles que estão interessados ​​na versão PySpark (na verdade, é o mesmo no Scala - veja o comentário abaixo):

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

    merchants_df_renamed.printSchema()

Resultado:

root
| - merchant_id: integer (nullable = true)
| - categoria: string (nullable = true)
| - subcategoria: string (nullable = true)
| - comerciante: string (nullable = true)

Tagar
fonte
1
Ao usar toDF()para renomear colunas no DataFrame, deve-se ter cuidado. Este método funciona muito mais devagar do que outros. Tenho o DataFrame que contém 100 milhões de registros e uma consulta de contagem simples leva cerca de 3s, enquanto a mesma consulta com o toDF()método leva cerca de 16s. Mas quando uso o select col AS col_newmétodo para renomear, recebo ~ 3s novamente. Mais de 5 vezes mais rápido! 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) } : _* )
}

Caso não seja óbvio, isso adiciona um prefixo e um sufixo a cada um dos nomes das colunas atuais. Isso pode ser útil quando você tem duas tabelas com uma ou mais colunas com o mesmo nome e deseja juntá-las, mas ainda assim ser capaz de eliminar a ambigüidade das colunas na tabela resultante. Com certeza, seria bom se houvesse uma maneira semelhante de fazer isso no SQL "normal".

Pedra Mylo
fonte
gosto com certeza, bom e elegante
thebluephantom
1

Suponha que o dataframe df tenha 3 colunas id1, nome1, preço1 e você deseja renomeá-las para id2, nome2, preço2

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

Achei essa abordagem útil em muitos casos.

Jagadeesh Verri
fonte
0

junção da tabela de reboque não renomear a chave associada

// 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)
}

trabalho!

Colin Wang
fonte