Предположим, что я делаю что-то вроде:
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...
но я действительно хотел year
как Int
(и, возможно, преобразовать некоторые другие столбцы).
Лучшее, что я мог придумать, -
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]
который немного запутан.
Я родом из R, и я привык писать, например.
df2 <- df %>%
mutate(year = year %>% as.integer,
make = make %>% toupper)
Скорее всего, я что-то пропустил, так как в искрах / scala... должен быть лучший способ сделать это...
Начиная с spark 2.x вы можете использовать .withColumn
. Проверьте документы здесь:
Начиная с версии Spark 1.4 вы можете применить метод приведения с DataType к столбцу:
import org.apache.spark.sql.types.IntegerType
val df2 = df.withColumn("yearTmp", df.year.cast(IntegerType))
.drop("year")
.withColumnRenamed("yearTmp", "year")
Если вы используете выражения SQL, вы также можете сделать:
val df2 = df.selectExpr("cast(year as int) year",
"make",
"model",
"comment",
"blank")
Для получения дополнительной информации проверьте документы: http://spark.apache.org/docs/1.6.0/api/scala/#org.apache.spark.sql.DataFrame
[EDIT: март 2016: спасибо за голоса! Хотя на самом деле это не лучший ответ, я думаю, что решения, основанные на withColumn
, withColumnRenamed
и cast
, выдвинутые msemelman, Martin Senne и другими, проще и чище].
Я думаю, что ваш подход в порядке, напомним, что Spark DataFrame
является (неизменным) RDD строк, поэтому мы никогда не заменяем столбец, просто создавая новый DataFrame
каждый раз с новой схемой.
Предполагая, что у вас есть исходный df со следующей схемой:
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)
И некоторые UDF определены в одном или нескольких столбцах:
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
)
Изменение типов столбцов или даже создание нового DataFrame из другого можно записать следующим образом:
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")
который дает:
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)
Это довольно близко к вашему собственному решению. Просто, сохраняя изменения типа и другие преобразования как отдельные udf val
, сделать код более читаемым и повторно использоваться.
NULL
или неправильная запись может привести к сбою всей работы. Не эффективен , потому что UDFs не является прозрачным для катализатора. Использование UDF для сложных операций - это нормально, но нет причин использовать их для приведения базовых типов. Это то, почему мы cast
метод (см ответа на Мартине Senne ). Чтобы сделать Catalyst прозрачным, требуется больше работы, но базовая безопасность - это всего лишь вопрос применения Try
and Option
.
Поскольку операция cast
доступна для Spark Column
(и поскольку я лично не одобряю udf
, как было предложено @Svend
в этой точке), как насчет:
df.select( df("year").cast(IntegerType).as("year"), ... )
чтобы применить к запрашиваемому типу? В качестве аккуратного побочного эффекта значения, не зависящие/ "конвертируемые" в этом смысле, станут null
.
Если вам нужно это как вспомогательный метод, используйте:
object DFHelper{
def castColumnTo( df: DataFrame, cn: String, tpe: DataType ) : DataFrame = {
df.withColumn( cn, df(cn).cast(tpe) )
}
}
который используется как:
import DFHelper._
val df2 = castColumnTo( df, "year", IntegerType )
Сначала, если вы хотите создать тип
import org.apache.spark.sql
df.withColumn("year", $"year".cast(sql.types.IntegerType))
С таким же именем столбца столбец будет заменен новым, вам не нужно добавлять и удалять.
Во-вторых, около Scala vs R. код Scala, наиболее похожий на R, который я могу достичь:
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)
}: _*
)
Хотя длина немного длиннее R.
Обратите внимание, что mutate
является функцией для кадра данных R, поэтому Scala очень хорош в выразительной мощности, заданной без использования специальной функции.
(df.columns
на удивление представляет собой Array [String] вместо Array [Column], возможно, они хотят, чтобы он выглядел как кадр данных Python pandas.)
Вы можете использовать selectExpr
, чтобы сделать его немного чище:
df.selectExpr("cast(year as int) as year", "upper(make) as make",
"model", "comment", "blank")
Java-код для изменения типа данных DataFrame от String до Integer
df.withColumn("col_name", df.col("col_name").cast(DataTypes.IntegerType))
Он просто преобразует существующий (тип данных String) в Integer.
DataTypes
нет DataTypes
sql.types
! это DataType
. Кроме того, можно просто импортировать IntegerType
и привести.
Чтобы преобразовать год из строки в int, вы можете добавить в csv-считыватель следующую опцию: "inferSchema" → "true", см. Документация DataBricks
Создайте простой набор данных, содержащий пять значений, и преобразуйте int
в string
тип:
val df = spark.range(5).select( col("id").cast("string") )
Итак, это действительно работает, если у вас есть проблемы с сохранением драйвера jdbc, такого как sqlserver, но он действительно полезен для ошибок, с которыми вы столкнетесь с синтаксисом и типами.
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)
df.select($"long_col".cast(IntegerType).as("int_col"))
Вы можете использовать приведенный ниже код.
df.withColumn("year", df("year").cast(IntegerType))
Который будет конвертировать столбец год в столбец IntegerType
.
ответы, предлагающие использовать литые, FYI, метод литья в искре 1.4.1 нарушен.
например, фреймворк данных со строковым столбцом, имеющим значение "8182175552014127960" при передаче в bigint, имеет значение "8182175552014128100"
df.show
+-------------------+
| a|
+-------------------+
|8182175552014127960|
+-------------------+
df.selectExpr("cast(a as bigint) a").show
+-------------------+
| a|
+-------------------+
|8182175552014128100|
+-------------------+
Нам пришлось столкнуться с большим количеством проблем, прежде чем найти эту ошибку, потому что у нас были большие столбцы в производстве.
Этот метод потеряет старый столбец и создаст новые столбцы с одинаковыми значениями и новым типом данных. Мои исходные типы данных при создании DataFrame были: -
root
|-- id: integer (nullable = true)
|-- flag1: string (nullable = true)
|-- flag2: string (nullable = true)
|-- name: string (nullable = true)
|-- flag3: string (nullable = true)
После этого я выполнил следующий код, чтобы изменить тип данных: -
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>)
После этого мой результат получился: -
root
|-- id: integer (nullable = true)
|-- flag2: string (nullable = true)
|-- name: string (nullable = true)
|-- flag1: boolean (nullable = true)
|-- flag3: boolean (nullable = true)
Можно изменить тип данных столбца, используя метод cast in spark sql. имя таблицы - это таблица, и в ней есть только два столбца: только столбцы column1 и column2 и тип данных столбца1 должны быть изменены. ex-spark.sql( "select cast (column1 as Double) column1NewName, column2 из таблицы" ) Вместо двойной записи введите свой тип данных.
По-другому:
// 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")
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()