Supposons que je fasse quelque chose comme:
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...
mais je voulais vraiment la year
comme Int
(et peut-être transformer d'autres colonnes).
Le mieux que je puisse trouver est
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]
ce qui est un peu compliqué.
Je viens de R et je suis habitué à pouvoir écrire, par exemple.
df2 <- df %>%
mutate(year = year %>% as.integer,
make = make %>% toupper)
Je manque probablement quelque chose, car il devrait y avoir un meilleur moyen de le faire en spark/scala ...
Depuis spark 2.x, vous pouvez utiliser .withColumn
. Vérifiez la documentation ici:
Depuis Spark version 1.4, vous pouvez appliquer la méthode de conversion avec DataType sur la colonne:
import org.Apache.spark.sql.types.IntegerType
val df2 = df.withColumn("yearTmp", df.year.cast(IntegerType))
.drop("year")
.withColumnRenamed("yearTmp", "year")
Si vous utilisez des expressions SQL, vous pouvez également faire:
val df2 = df.selectExpr("cast(year as int) year",
"make",
"model",
"comment",
"blank")
Pour plus d'informations, consultez la documentation: http://spark.Apache.org/docs/1.6.0/api/scala/#org.Apache.spark.sql.DataFrame
[EDIT: mars 2016: merci pour les votes! Bien que ce ne soit vraiment pas la meilleure réponse, je pense que les solutions basées sur withColumn
, withColumnRenamed
et cast
proposées par msemelman, Martin Senne et d'autres sont plus simples et plus propres].
Je pense que votre approche est correcte, rappelez-vous qu'un Spark DataFrame
est un RDD (immuable) de lignes, de sorte que nous ne sommes jamais vraiment en remplacement une colonne, créant simplement new DataFrame
à chaque fois avec un nouveau schéma.
En supposant que vous avez un df d'origine avec le schéma suivant:
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)
Et des UDF définis sur une ou plusieurs colonnes:
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
)
Changer les types de colonne ou même créer un nouveau DataFrame à partir d'un autre peut être écrit comme ceci:
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")
qui donne:
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)
C'est assez proche de votre propre solution. Simplement, garder les modifications de type et les autres transformations séparément udf val
s rend le code plus lisible et réutilisable.
Comme l'opération cast
est disponible pour les Spark Column
(et personnellement, je ne suis pas en faveur de udf
comme proposé par @Svend
à ce stade), que diriez-vous:
df.select( df("year").cast(IntegerType).as("year"), ... )
lancer sur le type demandé? Comme effet secondaire, les valeurs non convertibles/"convertibles" dans ce sens deviendront null
.
Si vous avez besoin de cela comme ne méthode d'assistance, utilisez:
object DFHelper{
def castColumnTo( df: DataFrame, cn: String, tpe: DataType ) : DataFrame = {
df.withColumn( cn, df(cn).cast(tpe) )
}
}
qui s'utilise comme:
import DFHelper._
val df2 = castColumnTo( df, "year", IntegerType )
First, si vous voulez utiliser un type, alors ceci:
import org.Apache.spark.sql
df.withColumn("year", $"year".cast(sql.types.IntegerType))
Avec le même nom de colonne, la colonne sera remplacée par une nouvelle. Vous n'avez pas besoin d'ajouter ou de supprimer des étapes.
Deuxième, à propos de Scala vs R.
C’est le code qui ressemble le plus à R que je peux trouver:
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)
}: _*
)
Bien que la longueur du code soit un peu plus longue que celle de R. Cela n'a rien à voir avec la verbosité de la langue. Dans R, la mutate
est une fonction spéciale pour le cadre de données R, tandis que dans Scala, vous pouvez facilement en créer une ad-hoc grâce à son pouvoir expressif.
Dans Word, cela évite des solutions spécifiques, car la base est suffisamment bonne pour vous permettre de créer rapidement et facilement vos propres fonctionnalités de langage de domaine.
note latérale: df.columns
est étonnamment un Array[String]
au lieu de Array[Column]
, peut-être veulent-ils que cela ressemble à Python le cadre de données de pandas.
Vous pouvez utiliser selectExpr
pour le rendre un peu plus propre:
df.selectExpr("cast(year as int) as year", "upper(make) as make",
"model", "comment", "blank")
Code Java permettant de modifier le type de données du DataFrame de String à Integer
df.withColumn("col_name", df.col("col_name").cast(DataTypes.IntegerType))
Il convertira simplement le type existant (type de données String) en Integer.
Pour convertir l'année de chaîne en entier, vous pouvez ajouter l'option suivante au lecteur csv: "inferSchema" -> "true", voir documentation DataBricks
Donc, cela ne fonctionne vraiment que si vous rencontrez des problèmes d'enregistrement sur un pilote jdbc tel que sqlserver, mais c'est vraiment utile pour les erreurs que vous rencontrerez avec la syntaxe et les types.
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)
Générez un jeu de données simple contenant cinq valeurs et convertissez int
en string
tapez:
val df = spark.range(5).select( col("id").cast("string") )
df.select($"long_col".cast(IntegerType).as("int_col"))
les réponses suggérant d'utiliser cast, FYI, la méthode de conversion utilisée dans spark 1.4.1 sont erronées.
par exemple, une trame de données avec une colonne de chaîne ayant la valeur "8182175552014127960" lorsqu'elle est convertie en bigint a la valeur "8182175552014128100"
df.show
+-------------------+
| a|
+-------------------+
|8182175552014127960|
+-------------------+
df.selectExpr("cast(a as bigint) a").show
+-------------------+
| a|
+-------------------+
|8182175552014128100|
+-------------------+
Nous avons dû faire face à beaucoup de problèmes avant de trouver ce bogue parce que nous avions des colonnes bigint en production.
En utilisant Spark Sql 2.4.0, vous pouvez le faire:
spark.sql("SELECT STRING(NULLIF(column,'')) as column_string")
Vous pouvez utiliser le code ci-dessous.
df.withColumn("year", df("year").cast(IntegerType))
Ce qui convertira année colonne en IntegerType
colonne.
Cette méthode supprimera l'ancienne colonne et créera de nouvelles colonnes avec les mêmes valeurs et le nouveau type de données. Mes types de données d'origine lors de la création du DataFrame étaient les suivants: -
root
|-- id: integer (nullable = true)
|-- flag1: string (nullable = true)
|-- flag2: string (nullable = true)
|-- name: string (nullable = true)
|-- flag3: string (nullable = true)
Après cela, j'ai exécuté le code suivant pour changer le type de données: -
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>)
Après cela, mon résultat s'est avéré être: -
root
|-- id: integer (nullable = true)
|-- flag2: string (nullable = true)
|-- name: string (nullable = true)
|-- flag1: boolean (nullable = true)
|-- flag3: boolean (nullable = true)
Si vous devez renommer des dizaines de colonnes en fonction de leur nom, l'exemple suivant adopte l'approche de @dnlbrky et l'applique à plusieurs colonnes à la fois:
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
}):_*)
Les colonnes non diffusées restent inchangées. Toutes les colonnes restent dans leur ordre d'origine.
Another solution is as follows:
1) Keep "inferSchema" as False
2) While running 'Map' functions on the row, you can read 'asString' (row.getString...)
<Code>
//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));
}
});
</Code>
On peut changer le type de données d’une colonne en utilisant la conversion dans spark sql. nom de table est table et il a deux colonnes seulement les types de données column1 et column2 et column1 doivent être changés. ex-spark.sql ("select cast (column1 as Double) column1NewName, column2 from table") À la place de double-écrire votre type de données.