如何在pyspark中更改列元数据?

时间:2017-05-30 22:47:49

标签: apache-spark pyspark metadata apache-spark-ml

如何在PySpark中更新列元数据? 我有元数据值对应于分类(字符串)功能的标称编码,我想以自动方式解码它们。除非重新创建架构,否则无法直接在pyspark API中编写元数据。是否有可能在运行时编辑PySpark中的元数据而无需将数据集转换为RDD并将其转换回来,前提是完整的模式描述(如here所述)?

示例清单:

# Create DF
df.show()

# +---+-------------+
# | id|     features|
# +---+-------------+
# |  0|[1.0,1.0,4.0]|
# |  1|[2.0,2.0,4.0]|
# +---+-------------+
# - That one has all the necessary metadata about what is encoded in feature column

# Slice one feature out
df = VectorSlicer(inputCol='features', outputCol='categoryIndex', indices=[1]).transform(df)
df = df.drop('features')
# +---+-------------+
# | id|categoryIndex|
# +---+-------------+
# |  0|        [1.0]|
# |  1|        [2.0]|
# +---+-------------+
# categoryIndex now carries metadata about singular array with encoding

# Get rid of the singular array
udf = UserDefinedFunction(lambda x: float(x[0]), returnType=DoubleType())
df2 = df.select(*[udf(column).alias(column) if column == 'categoryIndex' else column for column in df.columns])
# +---+-------------+
# | id|categoryIndex|
# +---+-------------+
# |  0|          1.0|
# |  1|          2.0|
# +---+-------------+
# - Metadata is lost for that one


# Write metadata
extract = {...}
df2.schema.fields[1].metadata = extract(df.schema.fields[1].metadata)
# metadata is readable from df2.schema.fields[1].metadata but is not affective. 
# Saving and restoring df from parque destroys the change
# Decode categorical
df = IndexToString(inputCol="categoryIndex", outputCol="category").transform(df)
# ERROR. Was supposed to decode the categorical values

Question提供了有关如何使用VectorAssembler,VectorIndexer以及如何通过使用StructType构建完整模式来添加元数据的见解,但却没有回答我的问题。

1 个答案:

答案 0 :(得分:8)

在这两种情况下,都需要丢失元数据:

  • 当您调用Python udf时,输入Column与其元数据和输出Column之间没有关系。 UserDefinedFunction(在Python和Scala中都是)Spark引擎的黑盒子。
  • 直接将数据分配给Python架构对象:

    df2.schema.fields[1].metadata = extract(df.schema.fields[1].metadata)
    

    根本不是一种有效的方法。 Spark DataFrame是JVM对象的包装器。 Python包装器中的任何更改对于JVM后端都是完全不透明的,并且根本不会传播:

    import json 
    
    df = spark.createDataFrame([(1, "foo")], ("k", "v"))
    df.schema[-1].metadata = {"foo": "bar"}
    
    json.loads(df._jdf.schema().json())
    
    ## {'fields': [{'metadata': {}, 'name': 'k', 'nullable': True, 'type': 'long'},
    ##   {'metadata': {}, 'name': 'v', 'nullable': True, 'type': 'string'}],
    ## 'type': 'struct'}
    

    甚至保存在Python中:

    df.select("*").schema[-1].metadata
    ## {}
    

Spark< 2.2 你可以使用一个小包装器(取自Spark Gotchas,由我和@eliasah维护):

def withMeta(self, alias, meta):
    sc = SparkContext._active_spark_context
    jmeta = sc._gateway.jvm.org.apache.spark.sql.types.Metadata
    return Column(getattr(self._jc, "as")(alias, jmeta.fromJson(json.dumps(meta))))

df.withColumn("foo", withMeta(col("foo"), "", {...}))

使用 Spark> = 2.2 ,您可以使用Column.alias

df.withColumn("foo", col("foo").alias("", metadata={...}))