在Spark Scala中重命名DataFrame的列名


93

我正在尝试转换DataFrameSpark-Scala 中a的所有标题/列名称。到目前为止,我想出了以下代码,该代码仅替换单个列名。

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

Answers:


237

如果结构平坦:

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)

您可以做的最简单的事情是使用toDFmethod:

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)

如果你想重命名各个列,你可以使用selectalias

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

可以很容易地概括为多列:

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

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

withColumnRenamed

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

与with foldLeft重命名多个列:

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

对于嵌套结构(structs),一种可能的选择是通过选择整个结构来重命名:

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)

请注意,这可能会影响nullability元数据。另一种可能性是通过强制重命名:

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)

要么:

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嗨,当使用withColumnRenamed时,我得到AnalysisException无法解析'CC8。1'给定的输入列...即使CC8.1在DataFrame中可用,也请失败。
unk1102'6

@ u449355我不清楚这是嵌套列还是包含点的列。在后一种情况下,反引号应该起作用(至少在某些基本情况下)。
zero323 '17

1
什么: _*)意思df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
Anton Kim

1
为了回答安东·金(Anton Kim)的问题:: _*scala是所谓的“ splat”运算符。基本上,它会将类似数组的内容爆炸到一个不包含列表的列表中,当您要将数组传递给带有任意数量args但没有带有a的版本的函数时,这很有用List[]。如果你在所有熟悉Perl,这之间的区别some_function(@my_array) # "splatted"some_function(\@my_array) # not splatted ... in perl the backslash "\" operator returns a reference to a thing
Mylo Stone

1
这句话对我来说真的很模糊df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)。尤其是lookup.getOrElse(c,c)部分。
Aetos

18

对于那些对PySpark版本感兴趣的人(实际上在Scala中是相同的-参见下面的评论):

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

    merchants_df_renamed.printSchema()

结果:


|-商人ID:整数(nullable = true)
|-类别:字符串(nullable = true)
|-子类别:字符串(nullable = true)
|-商人:字符串(nullable = true)


1
toDF()在数据帧重命名列一定要慎重。这种方法比其他方法慢得多。我有DataFrame包含100M记录,并且简单计数查询大约需要3s,而使用toDF()方法的相同查询大约需要16s。但是当使用select col AS col_new重命名方法时,我又得到了约3秒。快5倍以上!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) } : _* )
}

如果不是很明显,这会为每个当前列名添加一个前缀和后缀。当您有两个具有一个或多个具有相同名称的列的表,并且希望将它们联接但仍能够消除结果表中的列的歧义时,这将很有用。如果在“普通” SQL中有类似的方法可以做到这一点,那肯定会很好。


肯定喜欢它,美观大方
thebluephantom

1

假设数据框df具有3列id1,name1,price1,并且您希望将其重命名为id2,name2,price2

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

我发现这种方法在许多情况下都很有用。


0

拖车表联接不重命名联接键

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

作品!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.