Spark SQL'in DataFrame'indeki sütun türlerini nasıl değiştirebilirim?


152

Diyelim ki şöyle bir şey yapıyorum:

val df = sqlContext.load("com.databricks.spark.csv", Map("path" -> "cars.csv", "header" -> "true"))
df.printSchema()

root
 |-- year: string (nullable = true)
 |-- make: string (nullable = true)
 |-- model: string (nullable = true)
 |-- comment: string (nullable = true)
 |-- blank: string (nullable = true)

df.show()
year make  model comment              blank
2012 Tesla S     No comment
1997 Ford  E350  Go get one now th...

Ama gerçekten yearas istedim Int(ve belki de diğer bazı sütunları dönüştürmek).

Gelebileceğim en iyi şey

df.withColumn("year2", 'year.cast("Int")).select('year2 as 'year, 'make, 'model, 'comment, 'blank)
org.apache.spark.sql.DataFrame = [year: int, make: string, model: string, comment: string, blank: string]

bu biraz kıvrık.

R'den geliyorum ve yazmaya alışkınım, ör.

df2 <- df %>%
   mutate(year = year %>% as.integer,
          make = make %>% toupper)

Muhtemelen bir şey özlüyorum, çünkü Spark / Scala'da bunu yapmanın daha iyi bir yolu olmalı ...


Bu şekilde beğendim spark.sql ("SELECT STRING (NULLIF (sütun, '')) column_string olarak")
Eric Bellet

Yanıtlar:


141

Düzenleme: En yeni sürüm

Kıvılcım 2.x beri kullanabilirsiniz .withColumn. Dokümanları buradan kontrol edin:

https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.Dataset@withColumn(colName:String,col:org.apache.spark.sql.Column) : org.apache.spark.sql.DataFrame

En eski cevap

Spark 1.4 sürümünden beri, sütunda DataType ile cast yöntemini uygulayabilirsiniz:

import org.apache.spark.sql.types.IntegerType
val df2 = df.withColumn("yearTmp", df.year.cast(IntegerType))
    .drop("year")
    .withColumnRenamed("yearTmp", "year")

Sql ifadeleri kullanıyorsanız şunları da yapabilirsiniz:

val df2 = df.selectExpr("cast(year as int) year", 
                        "make", 
                        "model", 
                        "comment", 
                        "blank")

Daha fazla bilgi için dokümanları kontrol edin: http://spark.apache.org/docs/1.6.0/api/scala/#org.apache.spark.sql.DataFrame


4
neden Sütun ve ardından damla kullandınız? Yalnızca orijinal sütun adıyla Sütun ile kullanmak daha kolay değil mi?
Ameba Spugnosa

@AmebaSpugnosa Sanırım kullandığım zaman, Spark sütun adlarını tekrarlasaydı çöktü. Onları oluşturduğunuzda değil, kullandığınız zaman.
msemelman

5
sütunu ve ardından yeniden adlandırmayı bırakmanıza gerek yoktur. Bir satırda yapabilirsinizdf.withColumn("ctr", temp("ctr").cast(DecimalType(decimalPrecision, decimalScale)))
ruhong

1
Bu durumda yalnızca bir sütunu yeniden toplamak için yeni bir veri çerçevesi kopyası oluşturuldu mu? Bir şey mi kaçırıyorum? Ya da belki sahne arkasında bir optimizasyon var mı?
user1814008

5
Tarafından giderek dokümanlar arasında Spark 2.x, df.withColumn(..)olabilir eklemek veya değiştirmek bağlı bir sütun colNameargüman
y2k-Shubham

89

[EDIT: Mart 2016: oylar için teşekkürler! Gerçi gerçekten, bu şimdiye dayalı çözümler düşünmek, iyi cevap değil withColumn, withColumnRenamedve castmsemelman tarafından öne sürülen, Martin Senne ve diğerleri] daha basit ve temizdir.

Yaklaşımınızın iyi olduğunu düşünüyorum, bir Spark'ın DataFrame(değişmez) bir Satır RDD'si olduğunu hatırlayın , bu yüzden asla bir sütunu gerçekten değiştirmiyoruz , sadece DataFrameher seferinde yeni bir şema ile yeni oluşturuyoruz .

Aşağıdaki şemaya sahip orijinal bir df'niz olduğunu varsayarsak:

scala> df.printSchema
root
 |-- Year: string (nullable = true)
 |-- Month: string (nullable = true)
 |-- DayofMonth: string (nullable = true)
 |-- DayOfWeek: string (nullable = true)
 |-- DepDelay: string (nullable = true)
 |-- Distance: string (nullable = true)
 |-- CRSDepTime: string (nullable = true)

Ve bir veya birkaç sütunda tanımlanan bazı UDF'ler:

import org.apache.spark.sql.functions._

val toInt    = udf[Int, String]( _.toInt)
val toDouble = udf[Double, String]( _.toDouble)
val toHour   = udf((t: String) => "%04d".format(t.toInt).take(2).toInt ) 
val days_since_nearest_holidays = udf( 
  (year:String, month:String, dayOfMonth:String) => year.toInt + 27 + month.toInt-12
 )

Sütun türlerini değiştirmek, hatta diğerinden yeni bir DataFrame oluşturmak şu şekilde yazılabilir:

val featureDf = df
.withColumn("departureDelay", toDouble(df("DepDelay")))
.withColumn("departureHour",  toHour(df("CRSDepTime")))
.withColumn("dayOfWeek",      toInt(df("DayOfWeek")))              
.withColumn("dayOfMonth",     toInt(df("DayofMonth")))              
.withColumn("month",          toInt(df("Month")))              
.withColumn("distance",       toDouble(df("Distance")))              
.withColumn("nearestHoliday", days_since_nearest_holidays(
              df("Year"), df("Month"), df("DayofMonth"))
            )              
.select("departureDelay", "departureHour", "dayOfWeek", "dayOfMonth", 
        "month", "distance", "nearestHoliday")            

hangi verir:

scala> df.printSchema
root
 |-- departureDelay: double (nullable = true)
 |-- departureHour: integer (nullable = true)
 |-- dayOfWeek: integer (nullable = true)
 |-- dayOfMonth: integer (nullable = true)
 |-- month: integer (nullable = true)
 |-- distance: double (nullable = true)
 |-- nearestHoliday: integer (nullable = true)

Bu, kendi çözümünüze oldukça yakın. Basitçe, tür değişikliklerini ve diğer dönüşümleri ayrı olarak tutmak udf valkodu daha okunabilir ve yeniden kullanılabilir hale getirir.


26
Bu ne güvenli ne de verimlidir. Güvenli değil, çünkü tek NULLveya hatalı biçimlendirilmiş bir giriş tüm işi çökertecektir. Verimli değil çünkü UDF'ler Katalizöre karşı şeffaf değildir. Karmaşık işlemler için UDF'lerin kullanılması gayet iyi, ancak bunları temel tip döküm için kullanmak için bir neden yok. Bu yüzden castyöntemimiz var ( Martin Senne'nin cevabına bakınız ). Katalizöre şeffaf hale getirmek daha fazla iş gerektirir, ancak temel güvenlik sadece işe koyma Tryve Optionçalışma meselesidir .
zero323

Dize bugüne kadar "05-
NİSAN

3
Senin azaltmak için bir yolu var mı withColumn()tüm sütunları arasında dolaşır jenerik birine bölümü?
16'da Boern

Teşekkürler sıfır 323, bunu okuduktan sonra burada udf çözümünün neden çöktüğünü anladım. Bazı yorumlar SO bazı cevaplar daha iyidir :)
Simon Dirmeier

Bozuk satırı tanımanın herhangi bir yolu var mı, döküm sırasında yanlış veri türlerinin sütunlarına sahip kayıtlar anlamına gelir. Döküm işlevi olarak bu alanları boş yapar
Etisha

65

Gibi castoperasyon Spark için kullanılabilir Column'ın (ve şahsen lehine değil olarak udf' @ tarafından önerilen s Svendbu noktada), hakkında:

df.select( df("year").cast(IntegerType).as("year"), ... )

İstenen tipte yayınlamak için? Düzgün bir yan etki olarak, bu anlamda dökülebilir / "dönüştürülemez" değerler olur null.

Buna yardımcı bir yöntem olarak ihtiyacınız olması durumunda şunları kullanın:

object DFHelper{
  def castColumnTo( df: DataFrame, cn: String, tpe: DataType ) : DataFrame = {
    df.withColumn( cn, df(cn).cast(tpe) )
  }
}

hangi gibi kullanılır:

import DFHelper._
val df2 = castColumnTo( df, "year", IntegerType )

2
Bir sürü sütun dökmem ve yeniden adlandırmam gerekirse bana nasıl devam edeceğimi önerebilir misin (50 sütunum var ve skala için oldukça yeni, büyük bir çoğaltma oluşturmadan ona yaklaşmanın en iyi yolunun ne olduğundan emin değilim)? Bazı sütunlar String'de kalmalı, bazıları Float'a dökülmelidir.
Dmitry Smirnov

nasıl bir Dize bir Tarihe dönüştürmek için örneğin sütunda "25-APR-2016" ve "20160302"
dbspace

@DmitrySmirnov Hiç cevap aldınız mı? Aynı sorum var. ;)
Evan Zamir

@EvanZamir maalesef, diğer adımlarda verileri rdd olarak kullanabilmek için bir dizi işlem yaptım. Acaba bu günlerde kolaylaştı mı :)
Dmitry Smirnov

60

İlk olarak , yazı yazmak istiyorsanız, o zaman bu:

import org.apache.spark.sql
df.withColumn("year", $"year".cast(sql.types.IntegerType))

Aynı sütun adıyla, sütun yenisiyle değiştirilir. Adım ekleme ve silme işlemi yapmanız gerekmez.

İkinci hakkında Scala vs R .
Bu, RI'ye en çok benzeyen koddur:

val df2 = df.select(
   df.columns.map {
     case year @ "year" => df(year).cast(IntegerType).as(year)
     case make @ "make" => functions.upper(df(make)).as(make)
     case other         => df(other)
   }: _*
)

Kod uzunluğu R'lerden biraz daha uzun olmasına rağmen. Bu dilin ayrıntı düzeyi ile ilgisi yoktur. R ise mutate, R dataframe için özel bir fonksiyon ise Scala onun ifade gücü kolayca ad-hoc bir teşekkür yapabilirsiniz.
Kelime olarak, belirli çözümlerden kaçınır, çünkü dil tasarımı, kendi etki alanı dilinizi hızlı ve kolay bir şekilde oluşturmanıza yetecek kadar iyidir.


yan not: df.columnsşaşırtıcı bir şekilde Array[String]bunun yerine Array[Column], belki de Python pandaların veri çerçevesine benzemesini istiyorlar.


1
Pyspark'a eşdeğerini verir misiniz?
Harit Vishwakarma

"Yaş" alanım için "tanımın yasadışı başlangıcı" .withColumn ("yaş", $ "yaş" .cast (sql.types.DoubleType)) alıyorum. Herhangi bir öneri?
BlueDolphin

Bu dönüşümleri performans açısından birçok sütunda yapıyorsak veri çerçevesini .cache () yapmak zorunda mısınız yoksa Spark bunları optimize ettiği için gerekli değil mi?
skjagini

İçe aktarma sadece import org.apache.spark.sql.types._yerine olabilir . sql.types.IntegerTypeIntegerType
nessa.gp

17

selectExprBiraz daha temiz hale getirmek için kullanabilirsiniz :

df.selectExpr("cast(year as int) as year", "upper(make) as make",
    "model", "comment", "blank")

14

DataFrame'in veri tipini String'den Integer'a değiştirmek için Java kodu

df.withColumn("col_name", df.col("col_name").cast(DataTypes.IntegerType))

Sadece (String veri tipi) tamsayıya yayınlanacaktır.


1
Hiçbir var DataTypesiçinde sql.types! öyle DataType. Dahası, basitçe ithal IntegerTypeve döküm yapılabilir.
Ehsan M. Kermani

@ EhsanM.Kermani aslında DatyaTypes.IntegerType yasal bir referanstır.
Cupitor

1
@Cupitor DataTypes.IntegerTypeolması için kullanılır DeveloperAPI modu ve 's v.2.1.0 stabil
Ehsan M. Kermani

Bu en iyi çözüm!
Simon Dirmeier

8

Yılı dizeden int'e dönüştürmek için csv okuyucuya aşağıdaki seçeneği ekleyebilirsiniz: "inferSchema" -> "true", bkz. DataBricks belgeleri


5
Bu güzel çalışıyor ama yakalama, okuyucunun ikinci bir geçiş yapması gerektiğidir
beefyhalo

@beefyhalo kesinlikle yerinde, bunun herhangi bir yolu var mı?
Ayush

6

Bu sadece gerçekten sqlserver gibi bir jdbc sürücüsüne kaydetme sorunları yaşıyorsanız çalışır, ancak sözdizimi ve türleri ile karşılaşacağınız hatalar için gerçekten yararlıdır.

import org.apache.spark.sql.jdbc.{JdbcDialects, JdbcType, JdbcDialect}
import org.apache.spark.sql.jdbc.JdbcType
val SQLServerDialect = new JdbcDialect {
  override def canHandle(url: String): Boolean = url.startsWith("jdbc:jtds:sqlserver") || url.contains("sqlserver")

  override def getJDBCType(dt: DataType): Option[JdbcType] = dt match {
    case StringType => Some(JdbcType("VARCHAR(5000)", java.sql.Types.VARCHAR))
    case BooleanType => Some(JdbcType("BIT(1)", java.sql.Types.BIT))
    case IntegerType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case LongType => Some(JdbcType("BIGINT", java.sql.Types.BIGINT))
    case DoubleType => Some(JdbcType("DOUBLE PRECISION", java.sql.Types.DOUBLE))
    case FloatType => Some(JdbcType("REAL", java.sql.Types.REAL))
    case ShortType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case ByteType => Some(JdbcType("INTEGER", java.sql.Types.INTEGER))
    case BinaryType => Some(JdbcType("BINARY", java.sql.Types.BINARY))
    case TimestampType => Some(JdbcType("DATE", java.sql.Types.DATE))
    case DateType => Some(JdbcType("DATE", java.sql.Types.DATE))
    //      case DecimalType.Fixed(precision, scale) => Some(JdbcType("NUMBER(" + precision + "," + scale + ")", java.sql.Types.NUMERIC))
    case t: DecimalType => Some(JdbcType(s"DECIMAL(${t.precision},${t.scale})", java.sql.Types.DECIMAL))
    case _ => throw new IllegalArgumentException(s"Don't know how to save ${dt.json} to JDBC")
  }
}

JdbcDialects.registerDialect(SQLServerDialect)

Aynı kodu Java'da uygulamama yardımcı olabilir misiniz? ve customJdbcDialect'in DataFrame'e nasıl kaydedileceği
abhijitcaps

Güzel olan ben Vertica ile aynı şeyi yaptım, ama kıvılcım 2.1 beri. JDbc: Yalnızca ihtiyacınız olan belirli veri türünü uygulamanız gerekir. . dialect.getJDBCType (dt) .orElse (getCommonJDBCType (dt)) getOrElse (atmak yeni IllegalArgumentException (ler "$ için JDBC türü alınamıyor {dt.simpleString}"))
Arnon Rodman

6

Beş değer ve dönüştürmek içeren basit bir veri kümesi oluşturmak intiçin stringtip:

val df = spark.range(5).select( col("id").cast("string") )

6

Bunun benim için çok daha okunabilir olduğunu düşünüyorum.

import org.apache.spark.sql.types._
df.withColumn("year", df("year").cast(IntegerType))

Bu, yıl sütununuzu IntegerTypegeçici sütunlar oluşturarak ve bu sütunları bırakarak dönüştürecektir . Başka bir veri tipine dönüştürmek istiyorsanız, org.apache.spark.sql.typespaketin içindeki türleri kontrol edebilirsiniz .


5

1.4.1 kıvılcımında döküm, FYI, döküm yönteminin kullanılmasını öneren cevaplar bozuldu.

örneğin, bigint'e yayınlandığında "8182175552014127960" değerine sahip bir dize sütunu olan bir veri çerçevesi "8182175552014128100" değerine sahipse

    df.show
+-------------------+
|                  a|
+-------------------+
|8182175552014127960|
+-------------------+

    df.selectExpr("cast(a as bigint) a").show
+-------------------+
|                  a|
+-------------------+
|8182175552014128100|
+-------------------+

Bu hatayı bulmadan önce birçok sorunla karşı karşıya kaldık çünkü üretimde bigint sütunlarımız vardı.


4
kıvılcım yükseltmek
msemelman

2
@ msemelman küçük bir hata için üretimde yeni bir kıvılcım sürümüne yükseltmek çok saçma.
sauraI3h

her zaman küçük böcekler için her şeyi yükseltmeyiz mi? :)
caesarsol


4

Spark Sql 2.4.0 kullanarak şunları yapabilirsiniz:

spark.sql("SELECT STRING(NULLIF(column,'')) as column_string")

3

Aşağıdaki kodu kullanabilirsiniz.

df.withColumn("year", df("year").cast(IntegerType))

Yıl sütununu sütuna dönüştürür IntegerType.


2

Bu yöntem eski sütunu bırakır ve aynı değerlere ve yeni veri türüne sahip yeni sütunlar oluşturur. DataFrame oluşturulduğunda orijinal veri türlerim: -

root
 |-- id: integer (nullable = true)
 |-- flag1: string (nullable = true)
 |-- flag2: string (nullable = true)
 |-- name: string (nullable = true)
 |-- flag3: string (nullable = true)

Bundan sonra ben veri türünü değiştirmek için aşağıdaki kodu koştu: -

df=df.withColumnRenamed(<old column name>,<dummy column>) // This was done for both flag1 and flag3
df=df.withColumn(<old column name>,df.col(<dummy column>).cast(<datatype>)).drop(<dummy column>)

Bundan sonra sonucum şu şekilde çıktı: -

root
 |-- id: integer (nullable = true)
 |-- flag2: string (nullable = true)
 |-- name: string (nullable = true)
 |-- flag1: boolean (nullable = true)
 |-- flag3: boolean (nullable = true)

Lütfen çözümünüzü buraya iletir misiniz?
Ajay Kharade

1

Spark sql'de döküm kullanarak bir sütunun veri türünü değiştirebilirsiniz. tablo adı tablodur ve yalnızca iki sütun vardır sütun1 ve sütun2 ve sütun1 veri türü değiştirilir. ex-spark.sql ("cast (çift olarak sütun1) column1NewName, tablodan sütun2'yi seçin") Veri türünüzü iki kez yazın.


1

Adlarıyla verilen düzinelerce sütunu yeniden adlandırmanız gerekirse, aşağıdaki örnek @dnlbrky yaklaşımını alır ve aynı anda birkaç sütuna uygular:

df.selectExpr(df.columns.map(cn => {
    if (Set("speed", "weight", "height").contains(cn)) s"cast($cn as double) as $cn"
    else if (Set("isActive", "hasDevice").contains(cn)) s"cast($cn as boolean) as $cn"
    else cn
}):_*)

Yayınlanmamış sütunlar değiştirilmez. Tüm sütunlar orijinal sıralarında kalır.


1

Çok fazla cevap ve çok ayrıntılı açıklamalar yok

Aşağıdaki sözdizimi çalışır Databricks Notebook'u Spark 2.4 ile kullanma

from pyspark.sql.functions import *
df = df.withColumn("COL_NAME", to_date(BLDFm["LOAD_DATE"], "MM-dd-yyyy"))

Sahip olduğunuz giriş biçimini belirtmeniz gerektiğini (benim durumumda "AA-gg-yyyy") ve to_date bir kıvılcım sql işlevi olduğundan içe aktarmanın zorunlu olduğunu unutmayın.

Ayrıca bu sözdizimini denedim, ancak uygun bir döküm yerine null değerleri var:

df = df.withColumn("COL_NAME", df["COL_NAME"].cast("Date"))

(Not Sözdizimsel olarak doğru olması için parantez ve tırnak kullanmak zorundayım)


PS: Bu bir sözdizimi orman gibi olduğunu itiraf etmeliyim, giriş noktaları birçok olası yolları vardır ve resmi API referansları uygun örnekler eksik.


1
Sözdizimi orman. Evet. Bu şu an Spark dünyası.
conner.xyz

1

Başka bir çözüm aşağıdaki gibidir:

1) "inferSchema" yı Yanlış olarak saklayın

2) Satırdaki 'Harita' işlevlerini çalıştırırken, 'asString'i (row.getString ...) okuyabilirsiniz.

//Read CSV and create dataset
Dataset<Row> enginesDataSet = sparkSession
            .read()
            .format("com.databricks.spark.csv")
            .option("header", "true")
            .option("inferSchema","false")
            .load(args[0]);

JavaRDD<Box> vertices = enginesDataSet
            .select("BOX","BOX_CD")
            .toJavaRDD()
            .map(new Function<Row, Box>() {
                @Override
                public Box call(Row row) throws Exception {
                    return new Box((String)row.getString(0),(String)row.get(1));
                }
            });


0
    val fact_df = df.select($"data"(30) as "TopicTypeId", $"data"(31) as "TopicId",$"data"(21).cast(FloatType).as( "Data_Value_Std_Err")).rdd
    //Schema to be applied to the table
    val fact_schema = (new StructType).add("TopicTypeId", StringType).add("TopicId", StringType).add("Data_Value_Std_Err", FloatType)

    val fact_table = sqlContext.createDataFrame(fact_df, fact_schema).dropDuplicates()

0

Diğer yol:

// Generate a simple dataset containing five values and convert int to string type

val df = spark.range(5).select( col("id").cast("string")).withColumnRenamed("id","value")

0

Tek bir sütun adı belirtmeden belirli bir türde birden çok sütunu diğerine değiştirmek istiyorsanız

/* Get names of all columns that you want to change type. 
In this example I want to change all columns of type Array to String*/
    val arrColsNames = originalDataFrame.schema.fields.filter(f => f.dataType.isInstanceOf[ArrayType]).map(_.name)

//iterate columns you want to change type and cast to the required type
val updatedDataFrame = arrColsNames.foldLeft(originalDataFrame){(tempDF, colName) => tempDF.withColumn(colName, tempDF.col(colName).cast(DataTypes.StringType))}

//display

updatedDataFrame.show(truncate = false)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.