PySpark добавляет столбец в DataFrame из столбца TimeStampType

У меня есть DataFrame, который выглядит примерно так. Я хочу работать в день поля date_time.

root
 |-- host: string (nullable = true)
 |-- user_id: string (nullable = true)
 |-- date_time: timestamp (nullable = true)

Я попытался добавить столбец для извлечения дня. Пока мои попытки потерпели неудачу.

df = df.withColumn("day", df.date_time.getField("day"))

org.apache.spark.sql.AnalysisException: GetField is not valid on fields of type TimestampType;

Это также провалилось

df = df.withColumn("day", df.select("date_time").map(lambda row: row.date_time.day))

AttributeError: 'PipelinedRDD' object has no attribute 'alias'

Любая идея, как это можно сделать?

Ответ 1

Вы можете использовать простой map:

df.rdd.map(lambda row:
    Row(row.__fields__ + ["day"])(row + (row.date_time.day, ))
)

Другой вариант - зарегистрировать функцию и запустить SQL-запрос:

sqlContext.registerFunction("day", lambda x: x.day)
sqlContext.registerDataFrameAsTable(df, "df")
sqlContext.sql("SELECT *, day(date_time) as day FROM df")

Наконец, вы можете определить udf следующим образом:

from pyspark.sql.functions import udf
from pyspark.sql.types import IntegerType

day = udf(lambda date_time: date_time.day, IntegerType())
df.withColumn("day", day(df.date_time))

ИЗМЕНИТЬ

На самом деле, если вы используете raw SQL day, функция уже определена (по крайней мере, в Spark 1.4), поэтому вы можете опустить регистрацию udf. Он также предоставляет ряд различных функций обработки даты, включая:

  • например year, month, dayofmonth

  • инструменты для создания арифметических данных, такие как date_add, datediff

    /li >
  • синтаксические анализаторы, такие как from_unixtime и форматы, такие как date_format

Также можно использовать простые выражения даты, например:

current_timestamp() - expr("INTERVAL 1 HOUR")

Это означает, что вы можете создавать относительно сложные запросы, не передавая данные на Python. Например:

df =  sc.parallelize([
    (1, "2016-01-06 00:04:21"),
    (2, "2016-05-01 12:20:00"),
    (3, "2016-08-06 00:04:21")
]).toDF(["id", "ts_"])

now = lit("2016-06-01 00:00:00").cast("timestamp") 
five_months_ago = now - expr("INTERVAL 5 MONTHS")

(df
    # Cast string to timestamp
    # For Spark 1.5 use cast("double").cast("timestamp")
    .withColumn("ts", unix_timestamp("ts_").cast("timestamp"))
    # Find all events in the last five months
    .where(col("ts").between(five_months_ago, now))
    # Find first Sunday after the event
    .withColumn("next_sunday", next_day(col("ts"), "Sun"))
    # Compute difference in days
    .withColumn("diff", datediff(col("ts"), col("next_sunday"))))