我有一个数据集,其中包含一个包含时间段的记录列表(以纳秒表示:两个Longs,一个用于开始,一个用于结束)和测量值。我需要创建新的聚合数据集,该数据集只包含值更改的句点。例如:
input dataset:
+-----+-----+-----+
|start|end |value|
+-----+-----+-----+
|123 |124 |1 |
|124 |128 |1 |
|128 |300 |2 |
|300 |400 |2 |
|400 |500 |3 |
result dataset:
+-----+-----+-----+
|start|end |value|
+-----+-----+-----+
|123 |128 |1 |
|128 |400 |2 |
|400 |500 |3 |
我知道如何在小型数据集上执行此操作,但不知道如何使用mapreduce范例和Apache Spark。
你能否告诉我如何在Apache Spark,java中实现这个目标?
答案 0 :(得分:1)
这种方式看起来很简单。如果您使用groupBy找到min和max,然后合并数据集。
// df is original dataset
Dataset<Row> df_start = df.groupBy("value").min("start").withColumnRenamed("min(start)", "start").withColumnRenamed("value", "value_start");
Dataset<Row> df_end = df.groupBy("value").max("end").withColumnRenamed("max(end)", "end").withColumnRenamed("value", "value_end");
Dataset<Row> df_combined = df_start.join(df_end, df_start.col("value_start").equalTo(df_end.col("value_end"))).drop("value_end").withColumnRenamed("value_start", "value").orderBy("value");
df_combined.show(false);
+-----+-----+---+
|value|start|end|
+-----+-----+---+
|1 |123 |128|
|2 |128 |400|
|3 |400 |700|
+-----+-----+---+
答案 1 :(得分:0)
对此的一种方法是将您的问题标记为&#34;对于每个不同的值,找到该值的所有相邻时间范围并合并它们&#34;。根据这种理解,您可以对值使用groupBy
为每个值创建start
和end
的列表。然后,您可以使用自定义函数将这些函数折叠到连续的时间范围内。
在极端情况下,如果在数据集上使用仅磁盘持久性级别,唯一的要求是您可以将start_end
的单行放入内存中。对于大多数集群,这将此方法的上限设置为start_end
对每个值的gb。
这是一个示例实现(根据请求使用Java API - Scala会相当简洁):
public class JavaSparkTest {
public static void main(String[] args){
SparkSession session = SparkSession.builder()
.appName("test-changes-in-time")
.master("local[*]")
.getOrCreate();
StructField start = createStructField("start", DataTypes.IntegerType, false);
StructField end = createStructField("end", DataTypes.IntegerType, false);
StructField value = createStructField("value", DataTypes.IntegerType, false);
StructType inputSchema = createStructType(asList(start,end,value));
StructType startEndSchema = createStructType(asList(start, end));
session.udf().register("collapse_timespans",(WrappedArray<Row> startEnds) ->
JavaConversions.asJavaCollection(startEnds).stream()
.sorted((a,b)->((Comparable)a.getAs("start")).compareTo(b.getAs("start")))
.collect(new StartEndRowCollapsingCollector()),
DataTypes.createArrayType(startEndSchema)
);
Dataset<Row> input = session.createDataFrame(asList(
RowFactory.create(123, 124, 1),
RowFactory.create(124, 128, 1),
RowFactory.create(128, 300, 2),
RowFactory.create(300, 400, 2),
RowFactory.create(400, 500, 3),
RowFactory.create(500, 600, 3),
RowFactory.create(600, 700, 3)
), inputSchema);
Dataset<Row> startEndByValue = input.selectExpr("(start start, end end) start_end", "value");
Dataset<Row> startEndsByValue = startEndByValue.groupBy("value").agg(collect_list("start_end").as("start_ends"));
Dataset<Row> startEndsCollapsed = startEndsByValue.selectExpr("value", "explode(collapse_timespans(start_ends)) as start_end");
Dataset<Row> startEndsInColumns = startEndsCollapsed.select("value", "start_end.start", "start_end.end");
startEndsInColumns.show();
}
public static class StartEndRowCollapsingCollector implements Collector<Row, List<Row>, List<Row>>{
@Override
public Supplier<List<Row>> supplier() {
return ()-> new ArrayList<Row>();
}
@Override
public BiConsumer<List<Row>, Row> accumulator() {
return (rowList, row) -> {
// if there's no rows in the list or the start doesn't match the current end
if(rowList.size()==0 ||
!rowList.get(rowList.size()-1).getAs(1).equals(row.getAs(0))){
rowList.add(row);
} else {
Row lastRow = rowList.remove(rowList.size()-1);
rowList.add(RowFactory.create(lastRow.getAs(0), row.getAs(1)));
}
};
}
@Override
public BinaryOperator<List<Row>> combiner() {
return (a,b)->{ throw new UnsupportedOperationException();};
}
@Override
public Function<List<Row>, List<Row>> finisher() {
return i->i;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.EMPTY_SET;
}
}
}
程序输出:
+-----+-----+---+
|value|start|end|
+-----+-----+---+
| 1| 123|128|
| 3| 400|700|
| 2| 128|400|
+-----+-----+---+
请注意,值不是有序的。这是因为spark已经对数据集进行了分区并处理了值行,并且您还没有告诉它为行排序分配任何重要性。如果您需要时间或值排序输出,您当然可以按照通常的方式对其进行排序。