我正在尝试在 Spark-Scala 中转换 DataFrame
的所有标题/列名。到目前为止,我提出了以下代码,它只替换了一个列名。
for( i <- 0 to origCols.length - 1) {
df.withColumnRenamed(
df.columns(i),
df.columns(i).toLowerCase
);
}
如果结构是扁平的:
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)
您可以做的最简单的事情是使用 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)
如果要重命名各个列,可以使用 select
和 alias
:
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")
与 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)
对于那些对 PySpark 版本感兴趣的人(实际上它在 Scala 中是相同的 - 请参阅下面的评论):
merchants_df_renamed = merchants_df.toDF(
'merchant_id', 'category', 'subcategory', 'merchant')
merchants_df_renamed.printSchema()
结果:
根 |-- 商家 ID:整数(可为空 = true)|-- 类别:字符串(可空 = 真)|-- 子类别:字符串(可空 = 真)|-- 商家:字符串(可空 = 真)
toDF()
重命名 DataFrame 中的列必须小心。这种方法比其他方法工作得慢得多。我有 DataFrame 包含 100M 记录和简单的计数查询需要约 3 秒,而使用 toDF()
方法的相同查询需要约 16 秒。但是当使用 select col AS col_new
方法重命名时,我又得到了 ~3s。快 5 倍以上!火花 2.3.2.3
def aliasAllColumns(t: DataFrame, p: String = "", s: String = ""): DataFrame =
{
t.select( t.columns.map { c => t.col(c).as( p + c + s) } : _* )
}
如果不明显,这会为每个当前列名添加前缀和后缀。当您有两个具有相同名称的一个或多个列的表,并且您希望连接它们但仍然能够消除结果表中的列的歧义时,这可能很有用。如果在“普通”SQL 中有类似的方法可以做到这一点,那肯定会很好。
假设数据框 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)
我发现这种方法在很多情况下都很有用。
拖表连接不重命名连接键
// 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)
}
作品!
Sometime we have the column name is below format in SQLServer or MySQL table
Ex : Account Number,customer number
But Hive tables do not support column name containing spaces, so please use below solution to rename your old column names.
Solution:
val renamedColumns = df.columns.map(c => df(c).as(c.replaceAll(" ", "_").toLowerCase()))
df = df.select(renamedColumns: _*)
df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
中的: _*)
是什么意思: _*
是 scala 所谓的“splat”运算符。它基本上将一个类似数组的东西分解成一个不包含的列表,当您想将数组传递给一个接受任意数量的 args 但没有采用List[]
的版本的函数时,这很有用。如果您完全熟悉 Perl,那就是some_function(@my_array) # "splatted"
和some_function(\@my_array) # not splatted ... in perl the backslash "\" operator returns a reference to a thing
之间的区别。df.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
..你能分解一下吗?特别是lookup.getOrElse(c,c)
部分。