記事ディレクトリ
I.はじめに
環境:
windows11 64ビット
Python3.9
MySQL8
pandas1.4.2
この記事では、パンダを使用してMySQL にウィンドウ関数row_number()
、lead()/lag()
、rank()/dense_rank()
、を実装する方法と、この 2 つの違いを主に紹介します。first_value()
count()
sum()
注: Python は非常に柔軟な言語です。同じ目標を達成するために複数の方法がある場合があります。私が提供するのは解決策の 1 つにすぎません。他の方法がある場合は、メッセージを残して議論してください。
2. 文法的な比較
データシート
今回使用したデータは以下の通りです。
Python を使用してこのデータセットを構築するための構文は次のとおりです。
import pandas as pd
import numpy as np
df1 = pd.DataFrame({
'col1' : list(range(1,7))
,'col2' : ['AA','AA','AA','BB','BB','BB']#list('AABCA')
,'col3' : ['X',np.nan,'Da','Xi','Xa','xa']
,'col4' : [10,5,3,5,2,None]
,'col5' : [90,60,60,80,50,50]
,'col6' : ['Abc','Abc','bbb','Cac','Abc','bbb']
})
df2 = pd.DataFrame({
'col2':['AA','BB','CC'],'col7':[1,2,3],'col4':[5,6,7]})
df3 = pd.DataFrame({
'col2':['AA','DD','CC'],'col8':[5,7,9],'col9':['abc,bcd,fgh','rst,xyy,ijk','nml,opq,wer']})
注: jupyter のセルにコードを入れて実行するだけです。以下では、
df1
、を直接使用してdf2
、df3
対応するデータを呼び出します。
MySQL を使用してこのデータセットを構築するための構文は次のとおりです。
with t1 as(
select 1 as col1, 'AA' as col2, 'X' as col3, 10.0 as col4, 90 as col5, 'Abc' as col6 union all
select 2 as col1, 'AA' as col2, null as col3, 5.0 as col4, 60 as col5, 'Abc' as col6 union all
select 3 as col1, 'AA' as col2, 'Da' as col3, 3.0 as col4, 60 as col5, 'bbb' as col6 union all
select 4 as col1, 'BB' as col2, 'Xi' as col3, 5.0 as col4, 80 as col5, 'Cac' as col6 union all
select 5 as col1, 'BB' as col2, 'Xa' as col3, 2.0 as col4, 50 as col5, 'Abc' as col6 union all
select 6 as col1, 'BB' as col2, 'xa' as col3, null as col4, 50 as col5, 'bbb' as col6
)
,t2 as(
select 'AA' as col2, 1 as col7, 5 as col4 union all
select 'BB' as col2, 2 as col7, 6 as col4 union all
select 'CC' as col2, 3 as col7, 7 as col4
)
,t3 as(
select 'AA' as col2, 5 as col8, 'abc,bcd,fgh' as col9 union all
select 'DD' as col2, 7 as col8, 'rst,xyy,ijk' as col9 union all
select 'CC' as col2, 9 as col8, 'nml,opq,wer' as col9
)
select * from t1;
注: MySQL コード実行ボックスにコードを入れて実行するだけです。後で SQL コードを実行すると、データ セット (コードの 1 行目から 18 行目) がデフォルトで取得され、19 行目などのクエリ ステートメントのみが表示されます。
対応関係は次のとおりです。
Python データセット | MySQL データセット |
---|---|
df1 | t1 |
df2 | t2 |
df3 | t3 |
行番号()
row_number()
取得したデータの行番号を1から増加して計算します。これには通常、フィールドのグループ化とフィールドの並べ替えが含まれ、各グループの行番号は一意です。
MySQLrow_number()
関数を Python で使用するとgroupby()+rank()
、同様の効果を実現できます。
groupby()
単一の列が集約される場合は、列名を直接渡すだけです (たとえば、 )groupby('col2')
。複数の列の場合は、リストを渡します (たとえば、 )groupby(['col2','col6'])
。rank()
ソートできる列は 1 つだけです。たとえばdf.col2.rank()
、ソートする列が複数ある場合は、sort_values(['col6','col5']
最初にソートを使用し、次に集計を使用してから、累積関数cumcount()
またはソート関数を使用できますrank()
。
さらに、並べ替えフィールドに重複した値がある場合、MySQL ではランダムに値が返されますが、Python ではindex
デフォルトでさらに並べ替えに列が使用されることに注意してください。
具体的な例としては以下のようなものがあります。
1. 単一列のグループ化と単一列のソート
グループ化とソートの対象となる列が 1 つだけの場合は、Python でgroupby()
単一列の集計と単一列のソートを使用します。rank()
言語 | パイソン | MySQL |
---|---|---|
コード | df1_1 = df1.copy() df1_1['label'] = df1_1.groupby('col2')['col5'].rank(ascending=False,method='first') df1_1[[' col2','col5' 、'ラベル']] |
selectcol2,col5,row_number()over(partition bycol2 order bycol5 desc) t1 からラベルを選択します。 |
結果 |
2. 複数列グループ化、単一列ソート
複数列グループがある場合はgroupby()
関数にリストを渡します。
言語 | パイソン | MySQL |
---|---|---|
コード | df1_1 = df1.copy() df1_1['label'] = df1_1.groupby(['col2','col6'])['col5'].rank(ascending=True,method='first') df1_1[[ ' Col2','col6','col5','label']] |
t1 から、col2,col6,col5,row_number()over(col2 で分割、col5 で順序付け) ラベルを選択します。 |
結果 |
3. 単一列のグループ化、複数列の
ソート 複数列のソートの場合、[Python1] では最初にsort_values()
ソートを使用し、次にgroupby()
集計を使用し、次に を使用してrank()
ソート番号を追加しますが、[Python2] では比較的複雑です。と [Python1] の前 2 つのステップは同じです。実装番号は最後のステップで使用されますcumcount()
。
言語 | パイソン | MySQL |
---|---|---|
コード | 【Python1】 df1_1 = df1.copy() df1_1['label'] = df1_1.sort_values(['col6','col5'],ascending=[False,True]).groupby(['col2'])[' Col2'].rank(ascending=False,method='first') df1_1[['col2','col6','col5','label']] 【Python2】 df1_1 = df1.copy() df1_1['label '] = df1_1.sort_values(['col6','col5'],ascending=[False,True]).groupby(['col2']).cumcount()+1 df1_1[['col2','col6 ' ,'col5','ラベル']] |
selectcol2,col6,col5,row_number()over(col2 order bycol6 desc,col5) t1 からラベルを選択します。 |
結果 |
3. 複数列グループ化と複数列ソート 複数列
グループ化と複数列ソートでは、 【 3. 単一列グループ化と複数列ソートgroupby([])
】に基づいて、複数のグループ化フィールドをリストに直接追加できます。これ以上。
リード()/ラグ()
lead()
これは、現在の行から列の値を後方に取得することであり、指定された列を上に移動することとも解釈できます。逆に、lag()
列の値を現在の行から前方に取得することは、指定された列を移動することとも理解できます。指定された列を下に移動します。
並べ替えを使用すると、次のように 2 つを入れ替えることができます。
- 正の順序
lead()
== 逆の順序lag()
- 逆順
lead()
== 正順lag()
Python では、shift()
関数を使用して列の値を上下に移動できます。正の数が に渡されると列の値は下に移動し、負の数が に渡されると列の値は上に移動します。
注: 単一列/複数列のグループ化および単一列/複数列の並べ替えについては、ここを参照してくださいrow_number()
。繰り返しは行いません。
1. 1行を移動するには 1行を移動する場合は
、 MySQLのlead(col1)
/を直接lag(col1)
使用することも問題ありませんが、昇順と降順を組み合わせてカラム値の上下の移動を実現します。Python では、またはを使用して同じ効果を実現します。次の例は下に移動するため、 を使用します。lead(col1,1)
lag(col1,1)
shift(-1)
shift(1)
col1
shift(-1)
言語 | パイソン | MySQL |
---|---|---|
コード | df1_1 = df1.copy() df1_1['col1_2'] = df1_1.groupby(['col2']).col1.shift(-1) df1_1[['col2','col1','col1_2']].sort_values (['col2','col1'],ascending=[True,True]) |
【MySQL1】 select col2,col1,lead(col1)over(partition by col2 order by col1) col1_2 from t1; 【MySQL2】 select col2,col1,lag(col1)over(partition by col2 order by col1 desc) col1_2 from t1; |
结果 |
2、移动多行
移动多行的时候,MySQL 中需要指定移动行数,如下例子,移动2行,使用lead(col1,2)
或lag(col1,2)
,再结合升降序实现列值的上下移动。
在 Python 中,则修改传递给shift()
函数的参数值即可,如下例子,使用shift(2)
向上移动2行。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘col1_2’] = df1_1.groupby([‘col2’]).col1.shift(2) # 通过shift控制 df1_1[[‘col2’,‘col1’,‘col1_2’]].sort_values([‘col2’,‘col1’],ascending=[True,True]) |
【MySQL1】 select col2,col1,lead(col1,2)over(partition by col2 order by col1 desc) col1_2 from t1; 【MySQL2】 select col2,col1,lag(col1,2)over(partition by col2 order by col1) col1_2 from t1; |
结果 |
rank()/dense_rank()
rank()
和dense_rank()
用于计算排名。rank()
排名可能不连续,就是当有重复值的时候,会并列使用小的排名,而重复值之后的排名则按照重复个数叠加往后排,如一组数(10,20,20,30),按升序排列是(1,2,2,4);而dense_rank()
的排名是连续的,还是上面的例子,按升序排列是(1,2,2,3)。
而在 Python 中,排序同样是通过rank()
函数实现,只是method
和row_number()
使用的不一样。实现rank()
的效果,使method='min'
,而实现dense_rank()
的效果,使用method='dense'
。除了这两种和在row_number()
中使用的method='first'
,还有average
和max
。average
的逻辑是所有值进行不重复连续排序之后,将分组内的重复值的排名进行平均,还是上面的例子,按升序排列是(1,2.5,2.5,4),max
和min
相反,使用的是分组内重复值取大的排名进行排序,还是上面的例子,按升序排列是(1,3,3,4)。
同样地,排序字段如果有重复值,在 MySQL 中会随机返回,而 Python 中会默认使用index
列进一步排序。
注:关于单列/多列分组和单列/多列排序的情况,参考row_number()
,不再赘述。
1、rank()
Python 中使用rank(method='min')
实现 MySQL 中的rank()
窗口函数。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘label’] = df1_1.groupby([‘col2’])[‘col5’].rank(ascending=True,method=‘min’) df1_1[[‘col2’,‘col5’,‘label’]] |
select col2,col5,rank()over(partition by col2 order by col5) col1_2 from t1; |
结果 |
2、dense_rank()
Python 中使用rank(method='dense')
实现 MySQL 中的rank()
窗口函数。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘label’] = df1_1.groupby([‘col2’])[‘col5’].rank(ascending=True,method=‘dense’) df1_1[[‘col2’,‘col5’,‘label’]] |
select col2,col5,dense_rank()over(partition by col2 order by col5) col1_2 from t1; |
结果 |
first_value()
MySQL 中的窗口函数first_value()
是取第一个值,可用于取数据默认顺序的第一个值,也可以通过排序,取某一列的最大值或最小值。
在 Pandas 中,也有相同功能的函数first()
。
不过,first_value()
是窗口函数,不会影响表单内的其他字段,但first()
时一个普通函数,只返回表单中的第一个值对应的行,所以在 Python 中要实现first_value()
窗口函数相同的结果,需要将first()
函数返回的结果,再通过表联结关联回原表(具体例子如下)。在 Python 中,还有一个last()
函数,和first()
相反,结合排序,也可以实现相同效果,和first()
可互换,读者可自行测试,不再赘述。
注:关于单列/多列分组和单列/多列排序的情况,参考row_number()
,不再赘述。
1、取最大值
MySQL 中,对col5
降序,便可通过first_value()
取得最大值。同样,在 Python 中,使用sort_values()
对col5
进行降序,便可通过first()
取得最大值,然后再merge()
回原表。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_2 = df1_1.sort_values([‘col5’],ascending=[False]).groupby([‘col2’]).first().reset_index()[[‘col2’,‘col5’]] # 最好加个排序 df1[[‘col2’,‘col5’]].merge(df1_2,on = ‘col2’,how = ‘left’,suffixes=(‘’,‘_2’)) |
select col2,col5,first_value(col5)over(partition by col2 order by col5 desc) col5_2 from t1; |
结果 |
2、取最小值
取最小值,则是在取最大值的基础上,改变col5
的排序即可,由降序改为升序。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_2 = df1_1.sort_values([‘col5’],ascending=[True]).groupby([‘col2’]).first().reset_index()[[‘col2’,‘col5’]] df1[[‘col2’,‘col5’]].merge(df1_2,on = ‘col2’,how = ‘left’,suffixes=(‘’,‘_2’)) |
select col2,col5,first_value(col5)over(partition by col2 order by col5) col5_2 from t1; |
结果 |
count()/sum()
MySQL 的聚合函数count()
和sum()
等,也可以加上over()
实现窗口函数的效果。
count()
可以用于求各个分组内的个数,也可以对分组内某个列的值进行累计。sum()
可以用于对各个分组内某个列的值求和,也可以对分组某个列的值进行累加。
在 Python 中,针对累计和累加的功能,可以使用groupby()+cumcount()
和groupby()+cumsum()
实现(如下例子1和2),而针对分组内的计数和求和,可以通过groupby()+count()
和groupby()+sum()
实现(如下例子3和4)。
注:关于单列/多列分组和单列/多列排序的情况,参考row_number()
,不再赘述。
1、升序累计
Python 中使用sort_values()+groupby()+cumcount()
实现 MySQL count(<col_name>)over(partition by <col_name> order by <col_name>)
效果。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘col5_2’] = df1_1.sort_values([‘col5’,‘col1’],ascending=[True,False]).groupby(‘col2’).col5.cumcount()+1 df1_1[[‘col2’,‘col5’,‘col5_2’]] |
select col2,col5,count(col5)over(partition by col2 order by col5,col1) col5_2 from t1; |
结果 |
2、升序累加
Python 中使用sort_values()+groupby()+cumsum()
实现 MySQL sum(<col_name>)over(partition by <col_name> order by <col_name>)
效果。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘col5_2’] = df1_1.sort_values([‘col5’,‘col1’],ascending=[True,False]).groupby(‘col2’).col5.cumsum() df1_1[[‘col2’,‘col5’,‘col5_2’]] |
select col2,col5,sum(col5)over(partition by col2 order by col5,col1) col5_2 from t1; |
结果 |
3、分组计数
Python 中使用sort_values()+groupby()+count()
实现 MySQL count(<col_name>)over(partition by <col_name>)
效果。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_2 = df1_1.sort_values([‘col5’,‘col1’],ascending=[True,False]).groupby(‘col2’).col5.count().reset_index() df1_1[[‘col2’,‘col5’]].merge(df1_2,how=‘left’,on=‘col2’,suffixes=(‘’,‘_2’)) |
select col2,col5,count(col5)over(partition by col2) col5_2 from t1; |
结果 |
4、分组求和
Python 中使用sort_values()+groupby()+sum()
实现 MySQL sum(<col_name>)over(partition by <col_name>)
效果。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_2 = df1_1.sort_values([‘col5’,‘col1’],ascending=[True,False]).groupby(‘col2’).col5.sum().reset_index() df1_1[[‘col2’,‘col5’]].merge(df1_2,how=‘left’,on=‘col2’,suffixes=(‘’,‘_2’)) |
select col2,col5,sum(col5)over(partition by col2) col5_2 from t1; |
结果 |
三、小结
MySQL のウィンドウ関数の効果は、Python では基本的に複数のステップを経て、複数の関数を組み合わせて処理する必要があります。ウィンドウ関数にはフィールドのグループ化とフィールドの並べ替えが含まれ、それに対応してPython では と がgroupby()
使用されるため、基本的に Python でウィンドウ関数の効果を実現するには、これら 2 つの関数を使用してデータ処理を支援する必要があります。sort_values()
残りの集計形式は集計ウィンドウ関数の特性に従って変更され、対応関係は次のとおりです。
MySQL ウィンドウ関数 | Python の対応する関数 |
---|---|
行番号() | ランク() |
リード()/ラグ() | シフト() |
ランク()/デンス_ランク() | ランク() |
first_value() | 初め() |
カウント() | count()、cumcount() |
和() | sum()、cumsum() |