การเปลี่ยนชื่อคอลัมน์ของ DataFrame ใน Spark Scala


93

ฉันกำลังพยายามแปลงชื่อส่วนหัว / คอลัมน์ทั้งหมดของDataFrameใน Spark-Scala ณ ตอนนี้ฉันมาพร้อมกับรหัสต่อไปนี้ซึ่งแทนที่ชื่อคอลัมน์เดียวเท่านั้น

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

คำตอบ:


239

ถ้าโครงสร้างแบน:

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)

หากคุณต้องการเปลี่ยนชื่อคอลัมน์แต่ละคอลัมน์คุณสามารถใช้ได้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)

สวัสดี @ zero323 เมื่อใช้ withColumnRenamed ฉันได้รับ AnalysisException ไม่สามารถแก้ไข 'CC8 1 'คอลัมน์อินพุตที่ระบุ ... ล้มเหลวแม้ว่า CC8.1 จะมีอยู่ใน DataFrame โปรดแนะนำ
unk1102

@ u449355 ไม่ชัดเจนสำหรับฉันว่านี่คือคอลัมน์ที่ซ้อนกันหรือคอลัมน์ที่มีจุด ในกรณีหลังควรใช้ backticks (อย่างน้อยในบางกรณีพื้นฐาน)
zero323

1
สิ่งที่ไม่: _*)หมายถึงในdf.select(df.columns.map(c => col(c).as(lookup.getOrElse(c, c))): _*)
แอนตันคิม

1
เพื่อตอบคำถามของ Anton Kim: ตัว: _*ดำเนินการ scala ที่เรียกว่า "splat" โดยพื้นฐานแล้วมันจะระเบิดสิ่งที่เหมือนอาร์เรย์ลงในรายการที่ไม่มีการควบคุมซึ่งมีประโยชน์เมื่อคุณต้องการส่งอาร์เรย์ไปยังฟังก์ชันที่รับจำนวนอาร์กิวเมนต์โดยพลการ แต่ไม่มีเวอร์ชันที่ใช้ 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

19

สำหรับผู้ที่สนใจเวอร์ชัน PySpark (จริงๆแล้วมันเหมือนกันใน Scala - ดูความคิดเห็นด้านล่าง):

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

    merchants_df_renamed.printSchema()

ผลลัพธ์:

root
| - Merchant_id: integer (nullable = true)
| - category: string (nullable = true)
| - subcategory: string (nullable = true)
| - Merchant: string (nullable = true)


1
การใช้toDF()สำหรับการเปลี่ยนชื่อคอลัมน์ใน DataFrame ต้องระมัดระวัง วิธีนี้ทำงานช้ากว่าวิธีอื่นมาก ฉันมี DataFrame ที่มีเร็กคอร์ด 100M และการค้นหาการนับอย่างง่ายใช้เวลา ~ 3 วินาทีในขณะที่แบบสอบถามเดียวกันกับtoDF()วิธีการใช้เวลา ~ 16 วินาที แต่เมื่อใช้select col AS col_newวิธีการเปลี่ยนชื่อฉันได้รับ ~ 3s อีกครั้ง เร็วกว่า 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

สมมติว่า dataframe 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)
}

ได้ผล!


0
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: _*)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.