有没有一种方法可以在UDF中添加新列(在Java Spark中)

时间:2019-08-08 08:53:32

标签: java apache-spark user-defined-functions

我有一个spark数据集的列(在Java中),并且我希望该列的所有值都成为新列的列名(新列可以用常量值填充)。

For example I have:
+------------+
|    Column  | 
+------------+
| a          | 
| b          |
| c          |
+------------+

And I want: 
+------+----+----+---+
|Column| a  |  b | c |
+------+----+----+---+
| a    | 0  | 0  |0  |
| b    | 0  | 0  |0  |
| c    | 0  | 0  |0  |
+------+----+----+---+

我尝试过的是:

public class test{

    static SparkSession spark = SparkSession.builder().appName("Java")
            .config("spark.master", "local").getOrCreate();
    static Dataset<Row> dataset = spark.emptyDataFrame();

    public Dataset<Row> test(Dataset<Row> ds, SparkSession spark) {
        SQLContext sqlContext = new SQLContext(spark);
        sqlContext.udf().register("add", add, DataTypes.createArrayType(DataTypes.StringType));
        ds = ds.withColumn("substrings", functions.callUDF("add", ds.col("Column")));
        return ds;
    }

    private static UDF1 addSubstrings = new UDF1<String, String[]>() {
        public String[] call(String str) throws Exception {
            dataset = dataset.withColumn(str, functions.lit(0));
            String[] a = {"placeholder"};
            return a;
        }
    };
}

我的问题是,有时我得到正确的结果,有时却没有(没有添加列)。我真的不明白为什么。我正在寻找将数据集传递给UDF的方法,但我不知道如何。

目前,我正在通过使用列的collectAsList()解决此问题,然后迭代Arraylist并由此添加新列。但这确实效率很低,因为我有太多数据。

3 个答案:

答案 0 :(得分:2)

对于此用例,您可以使用pivot

ds
 .withColumn("pivot_column", $"first_column")
 .groupBy($"first_column")
 .pivot("pivot_column")
 .count

如果您想获得更好的性能,则可能需要像pivot("pivot_column", Seq("a", "b", "c"))一样在数据透视图中提供可能的值

我使用count进行汇总,但是您可以执行任何所需的汇总。

From
+------------+
|first_column| 
+------------+
| a          | 
| b          |
| c          |
+------------+

To

+------------+---+---+---+
|first_column| a | b | c |
+------------+---+---+---+
| a          | 1 | 0 | 0 |
| b          | 0 | 1 | 0 |
| c          | 0 | 0 | 1 |
+------------+---+---+---+

答案 1 :(得分:0)

如果Column的值最小/较小,请尝试以下代码。

df.show
+------+
|Column|
+------+
|     A|
|     B|
|     C|
+------+

// If you have multiple columns are exist, select only required column
val names = df.select($"Column").as[String].collect 
val df1 = names.foldLeft(df)((df,n) => df.withColumn(n, lit(0)))
df1.show()
+------+---+---+---+
|Column|  A|  B|  C|
+------+---+---+---+
|     A|  0|  0|  0|
|     B|  0|  0|  0|
|     C|  0|  0|  0|
+------+---+---+---+

答案 2 :(得分:0)

我认为Spark的本质(更确切地说是其并行性)不允许您使用UDF实现目标。

在执行查询时,Spark会将您的数据分发给执行器,每个执行器都有自己的一行行。每个行块都有自己的列Column的可能值列表。因此,每个执行者都将尝试添加自己的列列表,这与其他执行者的操作不同。 因此,当驱动程序尝试合并来自不同执行者的结果集时,它将失败(或者执行者自身也会失败)。

collectAsList确实解决了您的问题,尽管效率很低。

,您可以猜测列数并发明一些功能(适合您的实际数据)以将Column列获得的值映射到这些数,这样您就可以能够使每个执行器上的列集保持相等。这种解决方案不是很通用,但是可以解决某些情况。 即,您将获得类似以下的列:<c01, c02, c03, ..., cNN>