Python 和 SPL 对比 8——有序分组

人们对序运算天然是感兴趣的,分组运算也会涉及到次序。本文对比 Python  SPL 在有序分组的运算能力。

位置分组

成员的位置信息可能参与分组计算中,分组键也有可能是和成员位置(或序号)相关的。如:

现有如下数据:

time                       a                       b

0                          0.5                    -2.0

1                          0.5                    -2.0

2                          0.1                    -1.0

3                          0.1                    -1.0

4                          0.1                    -1.0

5                          0.5                    -1.0

6                          0.5                    -1.0

7                          0.5                    -3.0

8                          0.5                    -1.0

希望每三行分成一组,并把众数作为该组的结果。期望结果如下:

time                       a                       b

2                          0.5                    -2.0

5                          0.1                    -1.0

8                          0.5                    -1.0

Python

import pandas as   pd

file1="D:/data/position.txt"

data1=pd.read_csv(file1,sep="\t")

pos_seq=[i//3 for   i in range(len(data1))]

res1=data1.groupby(pos_seq).agg(lambda   x:x.mode().iloc[-1])

print(res1)

 

 

 

按位置衍生一列,按除 3 的整数商分组

按衍生列分组聚合

 

每 3 行分一组,只要序号除 3 取整就可以得到分组键,按此键分组即可。取众数也是聚合运算,用 agg+lambda 表达式就可以得到结果。

SPL


A

B

1

D:\data\position.txt


2

=file(A1).import@t()


3

=A2.group((#-1)\3).new(~.max(time):time,~.mode(a):a,~.mode(b):b)

/分组聚合

SPL可以利用”#”来得到分组键,不需要单独计算一列分组键。这里的分组键是整数,SPL 还有一种针对自然数分组的高效分组方法,那就是 group@n(),因为可以直接用序号找到正确的分组子集,不需要做比较运算,所以运算速度更快,这里稍微改造一下代码就行:

A3=A2.group@n((#+2)\3)...

值变化分组

有时数据本身是有序的,但分组时只希望把相邻的相同值分到一组,如:

有数据如下:

      duration  location  user

0        10    house    A

1         5    house    A

2         5      gym    A

3         4      gym    B

4        10     shop    B

5         4      gym    B

6         6      gym    B

当 user 和 location 连续相同时对 duration 进行求和,location 变化时则重新求和。期望结果如下:

   duration  location   user

        15    house    A

         5      gym    A

         4      gym    B

        10     shop    B

        10      gym    B

Python

file2="D:/data/value.txt"

data2=pd.read_csv(file2,sep="\t")

value_seq=(data2.location!=data2.location.shift()).cumsum()

res2=data2.groupby(['user','location',value_seq],   as_index=False, sort=False)['duration'].sum()

print(res2)

 

 

衍生列,相邻 location 相同该列值相等,不同则 +1

按 [user,location, 衍生列] 分组聚合

 

 

Python没有相邻成员相同分一组的功能,只能自己想办法衍生一列,把该列作为部分分组键分组才可以完成,而想出来比较简单的衍生列并没有那么容易,需要烧烧脑。

SPL


A

B


4

D:\data\value.txt


5

=file(A5).import@t()


6

=A6.groups@o(user,location;sum(duration))

/值变化分组

SPL 中 groups@o() 函数将依次扫描整个序列,当分组键值和上一个成员的分组键相同时,则将该成员加入到当前的分组子集,如果分组键值发生变化了,则产生一个新的分组子集并加入当前成员,扫描完之后就得到一批分组子集,从而完成分组运算。不需要自己烧脑去想衍生列,书写的方式也只是比 groups 多了个 @o 选项,这种分组只比较相邻值,分组速度更快,这是 Python 的 groupby 做不到的。

条件变化分组

数据本身有序时,还有一种情况就是条件变化时分一组。如:

现有数据如下:

ID          code

333_c_132   x

333_c_132   n06

333_c_132   n36

333_c_132   n60

333_c_132   n72

333_c_132   n84

333_c_132   n96

333_c_132   n108

333_c_132   n120

999_c_133   x

999_c_133   n06

999_c_133   n12

999_c_133   n24

998_c_134   x

998_c_134   n06

998_c_134   n12

998_c_134   n18

998_c_134   n36

997_c_135   x

997_c_135   n06

997_c_135   n12

997_c_135   n24

997_c_135   n36

996_c_136   x

996_c_136   n06

996_c_136   n12

996_c_136   n18

996_c_136   n24

996_c_136   n36

995_c_137   x

希望从 code 列的每两个 x 中间随机取一行

期望结果如下:

333_c_132   n06

999_c_133   n12

998_c_134   n18

997_c_135   n36

996_c_136   n18

Python

file3="D:/data/condition.txt"

data3=pd.read_csv(file3,sep="\t")

cond_seq=data3.code.eq('x').cumsum()

res3=data3[data3.code.ne('x')].groupby(cond_seq).apply(lambda   x:x.sample(1)).reset_index(level=0,drop=True)

print(res3)

 

 

衍生列

按衍生列分组再抽样

 

 

Python的思路和之前两个例子是一样的,都需要自己烧脑衍生出一列分组键,然后按这列来分组,在抽样时 Python 提供了抽样函数 sample(),使得抽样更加便捷

SPL


A

B


9

D:\data\condition.txt


10

=file(A9).import@t()


11

=A10.group@i(code=="x").conj((l=~.len(),if(l<2,,~.m(2:)(rand(l-1)+1))))

/条件变化分组

SPL 中 group@i() 的分组键是个表达式,每当计算出 true 时则产生一个新的分组子集,也就是满足某个条件时就重新分一组,同样不需要烧脑想衍生列,运算效率也更高,SPL 中暂时没有提供抽样函数,需要自己手动来写抽样这一动作,不过这对于 SPL 也很简单,利用 rand() 可以轻松完成。

 

这里要多说一句,@o 选项和 @i 选项既对在 groups 有效,也对 group 有效,效果也相同。

在日志处理时,本文介绍的三种有序分组都有很好的效果。

1.      固定行数日志处理

日志形式如下图:

..

第一行是 IP,TIME,GET,URL,BROWER;

第二行是 MODULE;

第三行是 USERID,UNAME,LOCATION

此时只要 group((#-1)\3),再处理各组的日志就可以了。

2.      行数不固定但每行都有标记的日志

日志形式如下:

..

每个用户的行数不同,但每行开始都有用户的 ID。

此时只要 group@o(~.split(“\t”)(1)),然后处理各组日志。

3.      行数不固定但有一个开始标记

日志形式如下:

..

每个用户的行数不同,但每个用户都有一个开始标记 userid。

此时只要 group@i(~.split(“:”)(1)==”userid”),然后处理各组日志。

小结

Python 对待有序和无序的分组是一样的,不能利用有序这一条件来提高效率,要想办法算出一个衍生列,让其符合分组的条件,是舍近求远的方法。

SPL 可以充分利用数据有序的优势,使得分组更快,也不需要自己去算衍生列,只要把分组条件写好就可以了,形式上也只是增加个选项 @n,@o 或 @i,简单实用。