示例:
有如下表需要進行行轉列:

代碼如下:

# -*- coding:utf-8 -*- import pandas as pd import MySQLdb from warnings import filterwarnings # 由於create table if not exists總會拋出warning,因此使用filterwarnings消除 filterwarnings('ignore', category = MySQLdb.Warning) from sqlalchemy import create_engine import sys if sys.version_info.major<3: reload(sys) sys.setdefaultencoding("utf-8") # 此腳本適用於python2和python3 host,port,user,passwd,db,charset="192.168.1.193",3306,"leo","mysql","test","utf8" def get_df(): global host,port,user,passwd,db,charset conn_config={"host":host, "port":port, "user":user, "passwd":passwd, "db":db,"charset":charset} conn = MySQLdb.connect(**conn_config) result_df=pd.read_sql('select UserName,Subject,Score from TEST',conn) return result_df def pivot(result_df): df_pivoted_init=result_df.pivot('UserName','Subject','Score') df_pivoted = df_pivoted_init.reset_index() # 將行索引也作為DataFrame值的一部分,以方便存儲數據庫 return df_pivoted_init,df_pivoted # 返回的兩個DataFrame,一個是以姓名作index的,一個是以數字序列作index,前者用於unpivot,后者用於save_to_mysql def unpivot(df_pivoted_init): # unpivot需要進行df_pivoted_init二維表格的行、列索引遍歷,需要拼SQL因此不能使用save_to_mysql存數據,這里使用SQL和MySQLdb接口存 insert_sql="insert into test_unpivot(UserName,Subject,Score) values " # 處理值為NaN的情況 df_pivoted_init=df_pivoted_init.fillna(0) for col in df_pivoted_init.columns: for index in df_pivoted_init.index: value=df_pivoted_init.at[index,col] if value!=0: insert_sql=insert_sql+"('%s','%s',%s)" %(index,col,value)+',' insert_sql = insert_sql.strip(',') global host, port, user, passwd, db, charset conn_config = {"host": host, "port": port, "user": user, "passwd": passwd, "db": db, "charset": charset} conn = MySQLdb.connect(**conn_config) cur=conn.cursor() cur.execute("create table if not exists test_unpivot like TEST") cur.execute(insert_sql) conn.commit() conn.close() def save_to_mysql(df_pivoted,tablename): global host, port, user, passwd, db, charset """ 只有使用sqllite時才能指定con=connection實例,其他數據庫需要使用sqlalchemy生成engine,engine的定義可以添加?來設置字符集和其他屬性 """ conn="mysql://%s:%s@%s:%d/%s?charset=%s" %(user,passwd,host,port,db,charset) mysql_engine = create_engine(conn) df_pivoted.to_sql(name=tablename, con=mysql_engine, if_exists='replace', index=False) # 從TEST表讀取源數據至DataFrame結構 result_df=get_df() # 將源數據行轉列為二維表格形式 df_pivoted_init,df_pivoted=pivot(result_df) # 將二維表格形式的數據存到新表test中 save_to_mysql(df_pivoted,'test') # 將被行轉列的數據unpivot,存入test_unpivot表中 unpivot(df_pivoted_init)
結果如下:
關於Pandas DataFrame類自帶的pivot方法:
DataFrame.pivot(index=None, columns=None, values=None):
Return reshaped DataFrame organized by given index / column values.
這里只有3個參數,pivot每次只能處理3個列,其中一個作為行轉列后的index,另一個作為行轉列之后的columns,最后一個作為行轉列后的表格values。
pivot會忽略除了以上3個列之外的其他列數據,因此需要使用DataFrame切片、聚合等操作來達成特定的行列轉換目標。
如下例:
補充說明:
在學習到Pandas的層次化索引部分時發現了2個很有意思的函數,也可以進行行列互轉,其用法如下:
(很久之后我才意識到,pivot只是封裝了unstack的一個快捷方式而已,其本質上還是先用set_index建立層次化索引,然后用unstack進行重塑,就像我在下面示例做的操作)
df=pd.DataFrame(np.random.randn(20).reshape(4,5),index=[['a','a','b','b'],[1,2,3,4]],columns=[10,20,30,40,50]) In [96]: df Out[96]: 10 20 30 40 50 a 1 0.945775 0.768337 0.851630 -1.050475 -1.102554 2 -0.366129 0.353388 -0.722637 -0.056877 1.178270 b 3 0.885536 0.210911 2.067309 1.283721 -0.432906 4 0.173504 1.263630 1.264698 0.913879 1.156815 In [98]: df.stack() Out[98]: a 1 10 0.945775 20 0.768337 30 0.851630 40 -1.050475 50 -1.102554 2 10 -0.366129 20 0.353388 30 -0.722637 40 -0.056877 50 1.178270 b 3 10 0.885536 20 0.210911 30 2.067309 40 1.283721 50 -0.432906 4 10 0.173504 20 1.263630 30 1.264698 40 0.913879 50 1.156815 In [99]: df.stack().unstack() Out[99]: 10 20 30 40 50 a 1 0.945775 0.768337 0.851630 -1.050475 -1.102554 2 -0.366129 0.353388 -0.722637 -0.056877 1.178270 b 3 0.885536 0.210911 2.067309 1.283721 -0.432906 4 0.173504 1.263630 1.264698 0.913879 1.156815
以上利用了Pandas的層次化索引,實際上這也是層次化索引一個主要的用途,結合本例我們可以把代碼改成如下:
result_df=pd.read_sql('select UserName,Subject,Score from TEST',conn) # 在從數據庫中獲取的數據格式是這樣的: UserName Subject Score 0 張三 語文 80.0 1 張三 數學 90.0 2 張三 英語 70.0 3 張三 生物 85.0 4 李四 語文 80.0 5 李四 數學 92.0 6 李四 英語 76.0 7 王五 語文 60.0 8 王五 數學 82.0 9 王五 英語 96.0 10 王五 生物 78.0 # 如果要使用層次化索引,那么我們只需要把UserName和Subject列設置為層次化索引,Score為其對應的值即可,我們借用set_index()函數: df=result_df.set_index(['UserName','Subject']) In [112]: df.unstack() Out[112]: Score Subject 數學 生物 英語 語文 UserName 張三 90.0 85.0 70.0 80.0 李四 92.0 NaN 76.0 80.0 王五 82.0 78.0 96.0 60.0 # 使用stack可以將unstack的結果轉回來,這樣就也在形式上實現了行列互轉,之后的操作基本一致了。