Python 使用Spark将列换行
我正在尝试将表中的一些列转换为行。 我正在使用Python和Spark 1.5.0。这是我的初始表格:Python 使用Spark将列换行,python,apache-spark,pivot,transpose,Python,Apache Spark,Pivot,Transpose,我正在尝试将表中的一些列转换为行。 我正在使用Python和Spark 1.5.0。这是我的初始表格: +-----+-----+-----+-------+ | A |col_1|col_2|col_...| +-----+-------------------+ | 1 | 0.0| 0.6| ... | | 2 | 0.6| 0.7| ... | | 3 | 0.5| 0.9| ... | | ...| ...| ...| ... | 我
+-----+-----+-----+-------+
| A |col_1|col_2|col_...|
+-----+-------------------+
| 1 | 0.0| 0.6| ... |
| 2 | 0.6| 0.7| ... |
| 3 | 0.5| 0.9| ... |
| ...| ...| ...| ... |
我想要这样的东西:
+-----+--------+-----------+
| A | col_id | col_value |
+-----+--------+-----------+
| 1 | col_1| 0.0|
| 1 | col_2| 0.6|
| ...| ...| ...|
| 2 | col_1| 0.6|
| 2 | col_2| 0.7|
| ...| ...| ...|
| 3 | col_1| 0.5|
| 3 | col_2| 0.9|
| ...| ...| ...|
有人知道我能行吗?谢谢您的帮助。使用平面地图。像下面这样的方法应该可以奏效
from pyspark.sql import Row
def rowExpander(row):
rowDict = row.asDict()
valA = rowDict.pop('A')
for k in rowDict:
yield Row(**{'A': valA , 'colID': k, 'colValue': row[k]})
newDf = sqlContext.createDataFrame(df.rdd.flatMap(rowExpander))
Spark局部线性代数库目前非常薄弱:它们不包括上述基本运算 对于Spark 2.1,有一个JIRA可以修复这个问题,但这对今天的您没有帮助 需要考虑的事项:执行转置可能需要完全洗牌数据 现在,您需要直接编写RDD代码。我用scala编写了
transpose
,但不是用python编写的。这是scala的版本:
def transpose(mat: DMatrix) = {
val nCols = mat(0).length
val matT = mat
.flatten
.zipWithIndex
.groupBy {
_._2 % nCols
}
.toSeq.sortBy {
_._1
}
.map(_._2)
.map(_.map(_._1))
.toArray
matT
}
因此,您可以将其转换为python供您使用。在这个特殊时刻,我没有足够的带宽来编写/测试:如果您无法进行转换,请告诉我
至少-以下内容可以很容易地转换为python
zipWithIndex
-->enumerate()
(与python等效-归功于@zero323)
map
-->[针对x英寸..的某些操作(x)]
groupBy
-->itertools.groupBy()
下面是flatte
的实现,它没有与python等效的版本:
def flatten(L):
for item in L:
try:
for i in flatten(item):
yield i
except TypeError:
yield item
因此,您应该能够将这些功能组合在一起,形成一个解决方案。使用基本的Spark SQL函数相对简单
Python
from pyspark.sql.functions import array, col, explode, struct, lit
df = sc.parallelize([(1, 0.0, 0.6), (1, 0.6, 0.7)]).toDF(["A", "col_1", "col_2"])
def to_long(df, by):
# Filter dtypes and split into column names and type description
cols, dtypes = zip(*((c, t) for (c, t) in df.dtypes if c not in by))
# Spark SQL supports only homogeneous columns
assert len(set(dtypes)) == 1, "All columns have to be of the same type"
# Create and explode an array of (column_name, column_value) structs
kvs = explode(array([
struct(lit(c).alias("key"), col(c).alias("val")) for c in cols
])).alias("kvs")
return df.select(by + [kvs]).select(by + ["kvs.key", "kvs.val"])
to_long(df, ["A"])
Scala:
import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.{array, col, explode, lit, struct}
val df = Seq((1, 0.0, 0.6), (1, 0.6, 0.7)).toDF("A", "col_1", "col_2")
def toLong(df: DataFrame, by: Seq[String]): DataFrame = {
val (cols, types) = df.dtypes.filter{ case (c, _) => !by.contains(c)}.unzip
require(types.distinct.size == 1, s"${types.distinct.toString}.length != 1")
val kvs = explode(array(
cols.map(c => struct(lit(c).alias("key"), col(c).alias("val"))): _*
))
val byExprs = by.map(col(_))
df
.select(byExprs :+ kvs.alias("_kvs"): _*)
.select(byExprs ++ Seq($"_kvs.key", $"_kvs.val"): _*)
}
toLong(df, Seq("A"))
我接受了@javadba编写的Scala答案,并创建了一个Python版本,用于在DataFrame
中转置所有列。这可能与OP的要求有点不同
from itertools import chain
from pyspark.sql import DataFrame
def _sort_transpose_tuple(tup):
x, y = tup
return x, tuple(zip(*sorted(y, key=lambda v_k: v_k[1], reverse=False)))[0]
def transpose(X):
"""Transpose a PySpark DataFrame.
Parameters
----------
X : PySpark ``DataFrame``
The ``DataFrame`` that should be tranposed.
"""
# validate
if not isinstance(X, DataFrame):
raise TypeError('X should be a DataFrame, not a %s'
% type(X))
cols = X.columns
n_features = len(cols)
# Sorry for this unreadability...
return X.rdd.flatMap( # make into an RDD
lambda xs: chain(xs)).zipWithIndex().groupBy( # zip index
lambda val_idx: val_idx[1] % n_features).sortBy( # group by index % n_features as key
lambda grp_res: grp_res[0]).map( # sort by index % n_features key
lambda grp_res: _sort_transpose_tuple(grp_res)).map( # maintain order
lambda key_col: key_col[1]).toDF() # return to DF
例如:
>>> X = sc.parallelize([(1,2,3), (4,5,6), (7,8,9)]).toDF()
>>> X.show()
+---+---+---+
| _1| _2| _3|
+---+---+---+
| 1| 2| 3|
| 4| 5| 6|
| 7| 8| 9|
+---+---+---+
>>> transpose(X).show()
+---+---+---+
| _1| _2| _3|
+---+---+---+
| 1| 4| 7|
| 2| 5| 8|
| 3| 6| 9|
+---+---+---+
df.selectExpr("stack(2, 'col_1', col_1, 'col_2', col_2) as (key, value)")
一种非常方便的实现方法:
from pyspark.sql import Row
def rowExpander(row):
rowDict = row.asDict()
valA = rowDict.pop('A')
for k in rowDict:
yield Row(**{'A': valA , 'colID' : k, 'colValue' : row[k]})
newDf = sqlContext.createDataFrame(df.rdd.flatMap(rowExpander)
使用函数create\u map
和explode
使用pyspark sql
解决问题的一种方法
from pyspark.sql import functions as func
#Use `create_map` to create the map of columns with constant
df = df.withColumn('mapCol', \
func.create_map(func.lit('col_1'),df.col_1,
func.lit('col_2'),df.col_2,
func.lit('col_3'),df.col_3
)
)
#Use explode function to explode the map
res = df.select('*',func.explode(df.mapCol).alias('col_id','col_value'))
res.show()
为了在pySpark
中转置数据帧,我在临时创建的列上使用pivot
,该列在操作结束时删除
喂,我们有一张这样的桌子。我们要做的是查找每个列出的\u days\u bin
值上的所有用户
+------------------+-------------+
| listed_days_bin | users_count |
+------------------+-------------+
|1 | 5|
|0 | 2|
|0 | 1|
|1 | 3|
|1 | 4|
|2 | 5|
|2 | 7|
|2 | 2|
|1 | 1|
+------------------+-------------+
创建新的临时列-“pvt\u值”
,对其进行聚合并透视结果
import pyspark.sql.functions as F
agg_df = df.withColumn('pvt_value', lit(1))\
.groupby('pvt_value')\
.pivot('listed_days_bin')\
.agg(F.sum('users_count')).drop('pvt_value')
新的数据帧应如下所示:
+----+---+---+
| 0 | 1 | 2 | # Columns
+----+---+---+
| 3| 13| 14| # Users over the bin
+----+---+---+
您可以使用堆栈功能:
例如:
>>> X = sc.parallelize([(1,2,3), (4,5,6), (7,8,9)]).toDF()
>>> X.show()
+---+---+---+
| _1| _2| _3|
+---+---+---+
| 1| 2| 3|
| 4| 5| 6|
| 7| 8| 9|
+---+---+---+
>>> transpose(X).show()
+---+---+---+
| _1| _2| _3|
+---+---+---+
| 1| 4| 7|
| 2| 5| 8|
| 3| 6| 9|
+---+---+---+
df.selectExpr("stack(2, 'col_1', col_1, 'col_2', col_2) as (key, value)")
其中:
- 2是要堆叠的列数(列1和列2)
- “col_1”是键的字符串
- col_1是从中获取值的列
如果您有多个列,您可以构建整个堆栈字符串,迭代列名并将其传递给selectExpr谢谢您的回答。但它不起作用。下面是我得到的错误消息:TypeError:tuple索引必须是整数,而不是str感谢您的回答。我不知道scala,但我会尽力理解你的代码。我将随时通知您。@Raouf上述代码在python中具有等价物。如果您很了解python,就不应该有问题。我展示了python中唯一缺少的flatten
。让我知道;)zipWithIndex
-->enumerate()
(Python等价物)?@zero323好眼睛!我将投票表决你的v好答案顺便说一句,谢谢。它稍微有点冗长,但不会移动太多数据。我不认为这“相对”简单:)我得到了错误AssertionError:所有列都必须是相同的类型
如何用Java编写它?如何做相反的事情。如何从第二个数据帧生成第一个数据帧?@ShekharKoirala这是因为数据帧中的列具有不同的数据类型,在函数代码中有明确的说明。另请参见和df。选择expr('column_names_to_keep','column_names_to_keep','stack(2,'col_1',col col u 2',col colu 2)as(key,value)“)你能看看这里吗?我想我正面临一个问题,因为我的回答很好,你能解释一下吗?你能看看这里吗?你能看看这里吗?你能看看这里吗?