J'ai un fichier CSV dans lequel un champ est datetime dans un format spécifique. Je ne peux pas l'importer directement dans mon Dataframe, car il doit s'agir d'un horodatage. Donc, je l'importe sous forme de chaîne et le convertis en Timestamp
comme ceci
import Java.sql.Timestamp
import Java.text.SimpleDateFormat
import Java.util.Date
import org.Apache.spark.sql.Row
def getTimestamp(x:Any) : Timestamp = {
val format = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss")
if (x.toString() == "")
return null
else {
val d = format.parse(x.toString());
val t = new Timestamp(d.getTime());
return t
}
}
def convert(row : Row) : Row = {
val d1 = getTimestamp(row(3))
return Row(row(0),row(1),row(2),d1)
}
Existe-t-il un moyen plus concis de procéder plus efficacement avec l’API Dataframe ou spark-sql? La méthode ci-dessus nécessite la création d'un RDD et de redonner le schéma pour le Dataframe.
Spark> = 2.2
Depuis que vous 2.2, vous pouvez fournir directement la chaîne de format:
import org.Apache.spark.sql.functions.to_timestamp
val ts = to_timestamp($"dts", "MM/dd/yyyy HH:mm:ss")
df.withColumn("ts", ts).show(2, false)
// +---+-------------------+-------------------+
// |id |dts |ts |
// +---+-------------------+-------------------+
// |1 |05/26/2016 01:01:01|2016-05-26 01:01:01|
// |2 |#$@#@# |null |
// +---+-------------------+-------------------+
Spark> = 1,6, <2,2
Vous pouvez utiliser les fonctions de traitement de date introduites dans Spark 1.5. En supposant que vous ayez les données suivantes:
val df = Seq((1L, "05/26/2016 01:01:01"), (2L, "#$@#@#")).toDF("id", "dts")
Vous pouvez utiliser unix_timestamp
pour analyser les chaînes et les convertir en horodatage.
import org.Apache.spark.sql.functions.unix_timestamp
val ts = unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss").cast("timestamp")
df.withColumn("ts", ts).show(2, false)
// +---+-------------------+---------------------+
// |id |dts |ts |
// +---+-------------------+---------------------+
// |1 |05/26/2016 01:01:01|2016-05-26 01:01:01.0|
// |2 |#$@#@# |null |
// +---+-------------------+---------------------+
Comme vous pouvez le constater, il couvre à la fois l'analyse et la gestion des erreurs. La chaîne de format doit être compatible avec Java SimpleDateFormat
.
Spark> = 1,5, <1,6
Vous devrez utiliser quelque chose comme ceci:
unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss").cast("double").cast("timestamp")
ou
(unix_timestamp($"dts", "MM/dd/yyyy HH:mm:ss") * 1000).cast("timestamp")
en raison de SPARK-11724 .
Spark <1.5
vous devriez pouvoir les utiliser avec expr
et HiveContext
.
Je n'ai pas encore joué avec Spark SQL mais je pense que ce serait plus scala (l'utilisation de null n'est pas considérée comme une bonne pratique):
def getTimestamp(s: String) : Option[Timestamp] = s match {
case "" => None
case _ => {
val format = new SimpleDateFormat("MM/dd/yyyy' 'HH:mm:ss")
Try(new Timestamp(format.parse(s).getTime)) match {
case Success(t) => Some(t)
case Failure(_) => None
}
}
}
Veuillez noter que je suppose que vous connaissez les types d'éléments Row
à l'avance (si vous le lisez à partir d'un fichier csv, ils sont tous String
), c'est pourquoi j'utilise un type correct comme String
et non Any
(tout est un sous-type de Any
).
Cela dépend également de la façon dont vous souhaitez gérer les exceptions d'analyse. Dans ce cas, si une exception d'analyse se produit, une None
est simplement renvoyée.
Vous pouvez l'utiliser plus tard avec:
rows.map(row => Row(row(0),row(1),row(2), getTimestamp(row(3))
J'ai un horodatage ISO8601 dans mon jeu de données et j'avais besoin de le convertir au format "aaaa-MM-jj". C'est ce que j'ai fait:
import org.joda.time.{DateTime, DateTimeZone}
object DateUtils extends Serializable {
def dtFromUtcSeconds(seconds: Int): DateTime = new DateTime(seconds * 1000L, DateTimeZone.UTC)
def dtFromIso8601(isoString: String): DateTime = new DateTime(isoString, DateTimeZone.UTC)
}
sqlContext.udf.register("formatTimeStamp", (isoTimestamp : String) => DateUtils.dtFromIso8601(isoTimestamp).toString("yyyy-MM-dd"))
Et vous pouvez simplement utiliser le fichier UDF dans votre requête spark SQL.
Je voudrais utiliser https://github.com/databricks/spark-csv
Cela déduira les horodatages pour vous.
import com.databricks.spark.csv._
val rdd: RDD[String] = sc.textFile("csvfile.csv")
val df : DataFrame = new CsvParser().withDelimiter('|')
.withInferSchema(true)
.withParseMode("DROPMALFORMED")
.csvRdd(sqlContext, rdd)
Je souhaite déplacer la méthode getTimeStamp
écrite par vous dans mapPartitions de rdd et réutiliser GenericMutableRow entre les lignes d'un itérateur:
val strRdd = sc.textFile("hdfs://path/to/cvs-file")
val rowRdd: RDD[Row] = strRdd.map(_.split('\t')).mapPartitions { iter =>
new Iterator[Row] {
val row = new GenericMutableRow(4)
var current: Array[String] = _
def hasNext = iter.hasNext
def next() = {
current = iter.next()
row(0) = current(0)
row(1) = current(1)
row(2) = current(2)
val ts = getTimestamp(current(3))
if(ts != null) {
row.update(3, ts)
} else {
row.setNullAt(3)
}
row
}
}
}
Et vous devriez toujours utiliser le schéma pour générer un DataFrame
val df = sqlContext.createDataFrame(rowRdd, tableSchema)
L'utilisation de GenericMutableRow dans une implémentation d'itérateur peut être trouvée dans Opérateur Agrégat , InMemoryColumnarTableScan , ParquetTableOperations etc.
J'ai eu quelques problèmes avec to_timestamp où il retournait une chaîne vide. Après beaucoup d'essais et d'erreurs, j'ai pu contourner le problème en exprimant l'horodatage, puis en faisant une chaîne. J'espère que cela aidera tout le monde avec le même problème:
df.columns.intersect(cols).foldLeft(df)((newDf, col) => {
val conversionFunc = to_timestamp(newDf(col).cast("timestamp"), "MM/dd/yyyy HH:mm:ss").cast("string")
newDf.withColumn(col, conversionFunc)
})