如何在Spark中转置RDD

时间:2015-04-01 12:21:26

标签: scala apache-spark rdd

我有这样的RDD:

1 2 3
4 5 6
7 8 9

这是一个矩阵。现在我想像这样转换RDD:

1 4 7
2 5 8
3 6 9

我该怎么做?

3 个答案:

答案 0 :(得分:15)

假设你有一个N×M矩阵。

如果N和M都很小,你可以在内存中容纳N×M项,那么使用RDD没有多大意义。但转置它很容易:

val rdd = sc.parallelize(Seq(Seq(1, 2, 3), Seq(4, 5, 6), Seq(7, 8, 9)))
val transposed = sc.parallelize(rdd.collect.toSeq.transpose)

如果N或M太大而您无法在内存中保存N或M条目,则您不能拥有此大小的RDD行。在这种情况下,无法表示原始矩阵或转置矩阵。

N和M可能是中等大小:您可以在内存中保存N或M个条目,但不能保存N×M个条目。在这种情况下,你必须将矩阵炸掉并再次放在一起:

val rdd = sc.parallelize(Seq(Seq(1, 2, 3), Seq(4, 5, 6), Seq(7, 8, 9)))
// Split the matrix into one number per line.
val byColumnAndRow = rdd.zipWithIndex.flatMap {
  case (row, rowIndex) => row.zipWithIndex.map {
    case (number, columnIndex) => columnIndex -> (rowIndex, number)
  }
}
// Build up the transposed matrix. Group and sort by column index first.
val byColumn = byColumnAndRow.groupByKey.sortByKey().values
// Then sort by row index.
val transposed = byColumn.map {
  indexedRow => indexedRow.toSeq.sortBy(_._1).map(_._2)
}

答案 1 :(得分:5)

没有使用collect()的初稿,所以一切都在工作方面运行,并且没有在驱动程序上完成任务:

val rdd = sc.parallelize(Seq(Seq(1, 2, 3), Seq(4, 5, 6), Seq(7, 8, 9)))

rdd.flatMap(row => (row.map(col => (col, row.indexOf(col))))) // flatMap by keeping the column position
   .map(v => (v._2, v._1)) // key by column position
   .groupByKey.sortByKey   // regroup on column position, thus all elements from the first column will be in the first row
   .map(_._2)              // discard the key, keep only value

此解决方案的问题在于,如果在分布式系统中执行操作,则转置矩阵中的列将最终进行混洗。会想到改进的版本

我的想法是,除了将“列号”附加到矩阵的每个元素之外,我们还附加了“行号”。因此,我们可以按列位置键入并按键重新组合,如示例中所示,但是我们可以对行号上的每一行重新排序,然后从结果中删除行/列号。 在将文件导入RDD时,我只是无法知道行号。

您可能认为将列和行号附加到每个矩阵元素很重要,但我想这是为了能够以分布式方式处理您的输入作为块并因此处理巨大矩阵而付出的代价。 / p>

当我找到订购问题的解决方案时,会更新答案。

答案 2 :(得分:4)

从Spark 1.6开始,您可以在DataFrame上使用pivot operation,具体取决于数据的实际形状,如果您将其放入DF,您可以将列转移到行,以下databricks blog是非常有用,因为它详细描述了一些带有代码示例的透视用例