記事ディレクトリ
I.はじめに
環境:
windows11 64ビット
Python3.9
MySQL8
pandas1.4.2
この記事では主に、Python と MySQL における行と列の変換に関するいくつかの一般的な問題の実装と構文の比較を紹介します。たとえば、複数の列が 1 つの列にマージされる、複数の行が 1 つの行にマージされる、1 つの列が複数の列に分割される、および 1 つの列が 1 つの列に分割される、などです。行は複数の行、複数行から複数列、複数列から複数行に分割されます。
注: 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
対応するデータを呼び出します。(データセット 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 行目などのクエリ ステートメントのみが表示されます。(このデータセット t3 は前のセクションとは異なり、より多くの
col9
列があることに注意してください)
対応関係は次のとおりです。
Python データセット | MySQL データセット |
---|---|
df1 | t1 |
df2 | t2 |
df3 | t3 |
concat (複数の列を 1 つの列にマージ)
MySQL はconcat()
複数のカラムを 1 つのカラムに連結できますが、この機能に加えて、concat_ws()
2 つの使用方法と返される結果にもいくつかの違いがあります。concat()
連結する必要があるすべてのフィールドまたは文字をカンマで区切るためであり、区切り文字をconcat_ws()
指定する必要があり、区切り文字は一律にデータを分割するために使用されます。詳細については、次の例を参照してください。
2 つの空ではないフィールドの連結 -concat/concat_ws
MySQL フィールドがすべて空ではない場合、結果は同じ区切り文字が使用された場合と同じconcat()
になります。Python では、、、 などのメソッドを使用して同じ結果を得ることができます。MySQLは暗黙的に数値フィールドを文字列に変換してからそれらを連結しますが、Python は手動で変換できないため、手動で変換する必要があることに注意してください。変換するには または を使用できます。concat_ws()
+
apply()+lambda
str.cat()
col5
astype()
map()
言語 | パイソン | MySQL |
---|---|---|
コード | 【Python1】 df1_1 = df1.copy() df1_1['col5'] = df1_1.col5.astype('str') df1_1.col2+'_'+df1_1.col5 【Python2】 df1_1 = df1.copy() df1_1[' Col5'] = df1_1.col5.astype('str') df1_1.apply(lambda x:x.col2+'_'+x.col5,axis=1) 【Python3】 df1_1 = df1.copy() df1_1['col5 '] = df1_1.col5.astype('str') df1_1.col2.str.cat(df1_1.col5,sep='_') |
【MySQL1】 t1からconcat(col2,'_',col5)をcol9として選択します。 【MySQL2】 t1からconcat_ws('_',col2,col5)をcol9として選択します。 |
結果 |
複数のフィールド (null 値を含む) の連結 - concat/concat_ws は、
フィールドに null 値が含まれる場合にconcat()
返しnull
、concat_ws()
null 以外の値の結合を返します。
2 つの非 null 値フィールドを使用する 3 つのメソッド (次のとおり) の結果も異なります。 と を使用した結果は+
すべてstr.cat()
返されますnull
([Python1] と [Python2] に示すように)。このメソッドを使用すると、apply()+lambda
null が処理されます。 value を としてnan
、すべてのフィールドが結合されます (以下のように [Python3])。
[Python1] と [Python2] はconcat()
同じ効果を実現しますが、[Python3] はconcat_ws()
まったく同じではありません。望ましい効果を実現したい場合はconcat_ws()
、[Python4] などの [Python3] に基づいた置換値処理のステップを追加し、空の文字列に置き換える.apply(lambda x:x.replace('_nan',''))
ことができます。_nan
言語 | パイソン | MySQL |
---|---|---|
コード | 【Python1】 df1_1 = df1.copy() df1_1['col5'] = df1_1.col5.astype('str') df1_1.col2+'_'+df1_1.col5+'_'+df1_1.col3 【Python2】 df1_1 = df1 .copy() df1_1['col5'] = df1_1.col5.astype('str') df1_1.col2.str.cat([df1_1.col5,df1_1.col3],sep='_') 【Python3】 df1_1 = df1.copy() df1_1['col5'] = df1_1.col5.astype('str') df1_1['col3'] = df1_1.col3.astype('str') df1_1.apply(lambda x:x.col2+' _'+x.col5+'_'+x.col3,axis=1) 【Python4】 df1_1 = df1.copy() df1_1['col5'] = df1_1.col5.astype('str') df1_1['col3' ] = df1_1.col3.astype('str') df1_1.apply(lambda x:x.col2+'_'+x.col5+'_'+x.col3,axis=1)\ .apply(lambda x:x. replace('_nan','')) |
select concat(col2,'_',col5,'_',col3) f_concat,concat_ws('_',col2,col5,col3) f_concat_ws から t1; |
結果 | [Python1 と Python2 の結果] [Python3 の結果] [Python4 の結果] |
group_concat (複数の行を 1 行に結合)
MySQLgroup_concat()
関数は通常、複数行のデータを 1 行のデータに集約するために使用されます。集計のプロセスでは、いくつかの異なる状況が関係します。1 つずつ見てみましょう。
単一フィールド (文字列) の集計、並べ替えなし
ここにはいくつかの制限があることに注意してください。文字列フィールドである必要があります。Python には暗黙的な変換がないため、文字列でない場合はデータ型を変換する必要があります。変換方法を参照してconcat()
、astype()
変換に使用します。
言語 | パイソン | MySQL |
---|---|---|
コード | 【Python1】 df2 = df1.groupby('col2').apply(lambda x:','.join(x.col6)).reset_index().rename(columns={0:'col6'}) df2 【 Python2 】 】 df1.groupby(“col2”).agg({“col6”: lambda x: ','.join(x)}).reset_index() |
selectcol2,group_concat(col6 separator ',') t1 グループからcol2 によってcol6; |
結果 |
聚合和排序为同一字段
当有排序时,不管聚合字段和排序字段是不是同个字段,在 MySQL 中加一个 order by 子句加上字段即可。
不过在 Python 的实现上二者有一些差异,同一字段时,可以在聚合的时候对列字段进行排序(如下 Python 代码)。
语言 | Python | MySQL |
---|---|---|
代码 | df1.groupby(“col2”).agg({“col6”: lambda x: ‘,’.join(sorted(x, reverse=True))}).reset_index() | select col2,group_concat(col6 order by col6 desc separator ‘,’) col6 from t1 group by col2; |
结果 |
聚合和排序为不同字段
当聚合字段和排序字段为不同字段时,就不可以使用上面的方法实现了,需要提前对数据排好序,再进行聚合(如下 Python 代码)。其实该语法通用性较好,同一字段也可以使用该语法,改一下排序的字段即可。
语言 | Python | MySQL |
---|---|---|
代码 | df1.sort_values(by=[‘col5’],ascending=False).groupby(“col2”).agg({“col6”: lambda x: ‘,’.join(x)}).reset_index() # 先排序,再聚合。第二排序默认index | select col2,group_concat(col6 order by col5 desc separator ‘,’) col6 from t1 group by col2; |
结果 |
去重聚合,聚合和排序必须为同一字段
去重聚合在 Python 中,可以看作是去重+同字段聚合排序的集合体,先去重,然后根据同一字段聚合排序的逻辑聚合起来。
语言 | Python | MySQL |
---|---|---|
代码 | 【Python1】 df1[[‘col2’,‘col6’]].drop_duplicates().sort_values(by=[‘col6’],ascending=False).groupby(“col2”).agg({“col6”: lambda x: ‘,’.join(x)}).reset_index() 【Python2】 df1.sort_values(by=[‘col6’],ascending=False).groupby(“col2”).agg({‘col6’:‘unique’}).agg({“col6”: lambda x: ‘,’.join(x)}).reset_index() 【Python3】 df1.sort_values(by=[‘col6’],ascending=False).groupby(“col2”).agg({‘col6’:‘unique’})[‘col6’].apply(lambda x:‘,’.join(x)).reset_index() |
select col2,group_concat(distinct col6 order by col6 desc separator ‘,’) col6 from t1 group by col2; |
结果 |
多字段拼接并聚合
多字段拼接并聚合本质上就是使用concat()
+group_concat()
进行实现。
语言 | Python | MySQL |
---|---|---|
代码 | df1_1 = df1.copy() df1_1[‘col5’] = df1_1.col5.astype(‘str’) df1_1.groupby(‘col2’).apply(lambda x:‘,’.join(x.col6+‘_’+x.col5)).reset_index().rename(columns={0:‘col6’}) |
select col2,group_concat(concat(col6,‘_’,col5) order by col6 separator ‘,’) col6 from t1 group by col2; |
结果 |
一列拆分为多列
一列拆为多列,一般是在某一列有某些特征明显的数据规律,然后通过这些规律将数据拆开,变成多个列。在 MySQL 中,像t3.col9
是有多个值通过逗号连在一起,这时候可以通过substring_index()
识别逗号进行拆分(如下 MySQL 代码)。
在 Python 中,可以直接使用apply(pd.Series,index=['col_1','col_2','col_3'])
实现对应的效果,然后再用pandas.concat()
把需求字段拼接返回,如下【Python1】。当然,也可以使用类似 MySQL 的逻辑,分别处理每一个列,将df3.col9
使用str.split()
拆分开,然后通过索引分别取值,如下【Python2】
语言 | Python | MySQL |
---|---|---|
代码 | 【Python1】 df1_1 = df3.col9.apply(lambda x:x.split(‘,’)).apply(pd.Series,index=[‘col_1’,‘col_2’,‘col_3’]) pd.concat([df3.col2,df1_1],axis=1) 【Python2】 df3_1 = df3.copy() df3_1[‘col_1’]=df3.col9.map(lambda x:x.split(‘,’)[0]) df3_1[‘col_2’]=df3.col9.map(lambda x:x.split(‘,’)[1]) df3_1[‘col_3’]=df3.col9.map(lambda x:x.split(‘,’)[2]) df3_1[[‘col2’,‘col_1’,‘col_2’,‘col_3’]] |
select col2,substring_index(col9,‘,’,1)col_1,substring_index(substring_index(col9,‘,’,-1),‘,’,-1)col_2,substring_index(col9,‘,’,-1)col_3 from t3; |
结果 |
一行拆分为多行
一行拆分多行和一列拆分多列从字面上看都是一变多的逻辑,不过实现过程大不同。在 MySQL 中,需要借助一个连续增加的列来实现增加行的效果,然后针对每一行再对拆分后的字段进行取舍,如下 MySQL 代码。
在 Python 中,有一个专门将列表数据沿行向展开接口:pandas.explode()
。只要将目标列的数据处理成列表的结构,便可直接使用它进行转化,如下 Python 代码。
语言 | Python | MySQL |
---|---|---|
代码 | pd.concat([df3.col2,df3.col9.apply(lambda x:x.split(‘,’))],axis=1).explode(“col9”) | select t3.col2,substring_index(substring_index(t3.col9,‘,’,t1.col1),‘,’,-1) col9 from t3 join t1 on t1.col1<=length(t3.col9)-length(replace(t3.col9,‘,’,‘’))+1 |
结果 |
多行转为多列
多行转多列,在 MySQL 中,通常是通过max(case when)
+group by
实现,具体语法如下 MySQL 代码。
在 Python 中,则可以使用透视表方法pivot_table()
实现。
语言 | Python | MySQL |
---|---|---|
代码 | pd.pivot_table(df1, values=‘col5’, index=[‘col6’],columns=[‘col2’], aggfunc=np.max).reset_index() | select col6,max(case when col2=‘AA’ then col5 end) “AA”,max(case when col2=‘BB’ then col5 end) “BB” from t1 group by col6; |
结果 |
多列转为多行
多列转多行,在 MySQL 中,其实就是将多列合并为一列,再通过一行转多行实现,即多列转为多行=多列转一列+一行拆分为多行(参考上文和以下 MySQL 代码)。
而 Python 中,有更多的实现方法,如下【Python1】,通过melt()
方法便可直接将列值拍平,降维返回。如下【Python2】,通过stack()
也可以实现相同的效果。二者不同点在于stack()
是对所有的字段进行操作,所以需要把要保留的字段设置为索引,并只取需要拍平的列进行操作,而melt()
更简便,直接指定对应的字段即可。
除了这两种方法,还可以使用和 MySQL 代码相同的逻辑,通过先拼接列再拆分行进行处理(如下【Python3】)
语言 | Python | MySQL |
---|---|---|
代码 | 【Python1】 df2.melt(id_vars=[‘col2’], value_vars=[‘col4’, ‘col7’],var_name=‘指标’, value_name=‘指标值’) # ignore_index=False不重置索引,默认重置 【Python2】 df2.set_index(‘col2’).stack().reset_index().rename(columns={‘level_1’:‘指标’,0:‘指标值’}) 【Python3】 df2_1 = df2.copy() df2_1[‘col4’] = df2_1.col4.astype(‘str’) df2_1[‘col7’] = df2_1.col7.astype(‘str’) df2_target = pd.concat([df2_1.col2,pd.Series([‘col4,col7’]*3).apply(lambda x:x.split(‘,’))],axis=1).explode(0).rename(columns={‘col2’:‘col’,0:‘指标’}) df2_value = pd.concat([df2_1.col2,df2_1.apply(lambda x:x.col4+‘,’+x.col7,axis=1).apply(lambda x:x.split(‘,’))],axis=1).explode(0).rename(columns={0:‘指标值’}) pd.concat([df2_target,df2_value],axis=1)[[‘col2’,‘指标’,‘指标值’]] |
select t2.col2,if(t1.col1=1,'col4','col7') "index",substring_index(substring_index(concat(t2.col4,',',t2.col7),',',t1. Col1),',',-1) t2 join t1の「インジケーター値」 t1.col1<=2 |
結果 |
3. まとめ
MySQL と Python はどちらも、行と列の変換を扱う際に非常に柔軟です。この 2 つと比較すると、Python の方が若干優れており、より多くのインターフェイスを備えているため、より柔軟にデータを処理し、目的の効果を達成することができます。ただし、MySQL は比較的簡潔で、より統一された形式とより単純な構文を備えています。