在Spark RDD和/或Spark DataFrame中重新整形/透视数据

Jas*_*son 25 python pivot apache-spark apache-spark-sql pyspark

我有以下格式的数据(RDD或Spark DataFrame):

from pyspark.sql import SQLContext
sqlContext = SQLContext(sc)

 rdd = sc.parallelize([('X01',41,'US',3),
                       ('X01',41,'UK',1),
                       ('X01',41,'CA',2),
                       ('X02',72,'US',4),
                       ('X02',72,'UK',6),
                       ('X02',72,'CA',7),
                       ('X02',72,'XX',8)])

# convert to a Spark DataFrame                    
schema = StructType([StructField('ID', StringType(), True),
                     StructField('Age', IntegerType(), True),
                     StructField('Country', StringType(), True),
                     StructField('Score', IntegerType(), True)])

df = sqlContext.createDataFrame(rdd, schema)
Run Code Online (Sandbox Code Playgroud)

我想做的是'重塑'数据,将Country(特别是美国,英国和CA)中的某些行转换为列:

ID    Age  US  UK  CA  
'X01'  41  3   1   2  
'X02'  72  4   6   7   
Run Code Online (Sandbox Code Playgroud)

从本质上讲,我需要Python的pivot工作流程:

categories = ['US', 'UK', 'CA']
new_df = df[df['Country'].isin(categories)].pivot(index = 'ID', 
                                                  columns = 'Country',
                                                  values = 'Score')
Run Code Online (Sandbox Code Playgroud)

我的数据集相当大,所以我不能真正地collect()将数据摄取到内存中来进行Python本身的重塑.有没有办法.pivot()在映射RDD或Spark DataFrame时将Python 转换为可调用的函数?任何帮助,将不胜感激!

zer*_*323 20

从Spark 1.6开始,您可以使用pivot函数GroupedData并提供聚合表达式.

pivoted = (df
    .groupBy("ID", "Age")
    .pivot(
        "Country",
        ['US', 'UK', 'CA'])  # Optional list of levels
    .sum("Score"))  # alternatively you can use .agg(expr))
pivoted.show()

## +---+---+---+---+---+
## | ID|Age| US| UK| CA|
## +---+---+---+---+---+
## |X01| 41|  3|  1|  2|
## |X02| 72|  4|  6|  7|
## +---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)

级别可以省略,但如果提供,可以提高性能并充当内部过滤器.

这种方法仍然相对较慢,但肯定会在JVM和Python之间手动传递数据.


小智 7

首先,这可能不是一个好主意,因为你没有得到任何额外的信息,但是你用固定的模式约束自己(即你必须知道你期望的国家数量,当然还有其他的国家意味着改变代码)

话虽如此,这是一个SQL问题,如下所示.但是如果你认为它不是"类似软件"(严重的话,我听过这个!!),那么你可以参考第一个解决方案.

解决方案1:

def reshape(t):
    out = []
    out.append(t[0])
    out.append(t[1])
    for v in brc.value:
        if t[2] == v:
            out.append(t[3])
        else:
            out.append(0)
    return (out[0],out[1]),(out[2],out[3],out[4],out[5])
def cntryFilter(t):
    if t[2] in brc.value:
        return t
    else:
        pass

def addtup(t1,t2):
    j=()
    for k,v in enumerate(t1):
        j=j+(t1[k]+t2[k],)
    return j

def seq(tIntrm,tNext):
    return addtup(tIntrm,tNext)

def comb(tP,tF):
    return addtup(tP,tF)


countries = ['CA', 'UK', 'US', 'XX']
brc = sc.broadcast(countries)
reshaped = calls.filter(cntryFilter).map(reshape)
pivot = reshaped.aggregateByKey((0,0,0,0),seq,comb,1)
for i in pivot.collect():
    print i
Run Code Online (Sandbox Code Playgroud)

现在,解决方案2:当然更好,因为SQL是正确的工具

callRow = calls.map(lambda t:   

Row(userid=t[0],age=int(t[1]),country=t[2],nbrCalls=t[3]))
callsDF = ssc.createDataFrame(callRow)
callsDF.printSchema()
callsDF.registerTempTable("calls")
res = ssc.sql("select userid,age,max(ca),max(uk),max(us),max(xx)\
                    from (select userid,age,\
                                  case when country='CA' then nbrCalls else 0 end ca,\
                                  case when country='UK' then nbrCalls else 0 end uk,\
                                  case when country='US' then nbrCalls else 0 end us,\
                                  case when country='XX' then nbrCalls else 0 end xx \
                             from calls) x \
                     group by userid,age")
res.show()
Run Code Online (Sandbox Code Playgroud)

数据设置:

data=[('X01',41,'US',3),('X01',41,'UK',1),('X01',41,'CA',2),('X02',72,'US',4),('X02',72,'UK',6),('X02',72,'CA',7),('X02',72,'XX',8)]
 calls = sc.parallelize(data,1)
countries = ['CA', 'UK', 'US', 'XX']
Run Code Online (Sandbox Code Playgroud)

结果:

从第一个解决方案

(('X02', 72), (7, 6, 4, 8)) 
(('X01', 41), (2, 1, 3, 0))
Run Code Online (Sandbox Code Playgroud)

从第二个解决方案

root  |-- age: long (nullable = true)  
      |-- country: string (nullable = true)  
      |-- nbrCalls: long (nullable = true)  
      |-- userid: string (nullable = true)

userid age ca uk us xx 
 X02    72  7  6  4  8  
 X01    41  2  1  3  0
Run Code Online (Sandbox Code Playgroud)

请告诉我这是否有效,或不:)

最好的Ayan

  • 这是大多数数据语言/框架(SAS,Scalding,Pandas等)中存在的相当普遍的功能。希望这能尽快将其引入Spark。 (2认同)

pat*_*rry 5

这是一种本机Spark方法,它不对列名进行硬连接。它基于aggregateByKey,并使用字典来收集为每个键显示的列。然后,我们收集所有列名称以创建最终的数据框。[在为每个记录发出字典后,早期版本使用jsonRDD,但这效率更高。]限制到特定的列列表,或者排除类似的列,XX将很容易进行修改。

即使在很大的桌子上,性能似乎也不错。我正在使用一种变体,它计算每个ID发生可变数量的事件中的每一个事件的次数,每种事件类型生成一列。除了使用collections.Counter而不是中的dict seqFn来计算出现次数外,代码基本相同。

from pyspark.sql.types import *

rdd = sc.parallelize([('X01',41,'US',3),
                       ('X01',41,'UK',1),
                       ('X01',41,'CA',2),
                       ('X02',72,'US',4),
                       ('X02',72,'UK',6),
                       ('X02',72,'CA',7),
                       ('X02',72,'XX',8)])

schema = StructType([StructField('ID', StringType(), True),
                     StructField('Age', IntegerType(), True),
                     StructField('Country', StringType(), True),
                     StructField('Score', IntegerType(), True)])

df = sqlCtx.createDataFrame(rdd, schema)

def seqPivot(u, v):
    if not u:
        u = {}
    u[v.Country] = v.Score
    return u

def cmbPivot(u1, u2):
    u1.update(u2)
    return u1

pivot = (
    df
    .rdd
    .keyBy(lambda row: row.ID)
    .aggregateByKey(None, seqPivot, cmbPivot)
)
columns = (
    pivot
    .values()
    .map(lambda u: set(u.keys()))
    .reduce(lambda s,t: s.union(t))
)
result = sqlCtx.createDataFrame(
    pivot
    .map(lambda (k, u): [k] + [u.get(c) for c in columns]),
    schema=StructType(
        [StructField('ID', StringType())] + 
        [StructField(c, IntegerType()) for c in columns]
    )
)
result.show()
Run Code Online (Sandbox Code Playgroud)

产生:

ID  CA UK US XX  
X02 7  6  4  8   
X01 2  1  3  null
Run Code Online (Sandbox Code Playgroud)