pandas.DataFrame的pivot()和unstack()實現行轉列


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

代碼如下:

# -*- 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)
View Code

 結果如下:

關於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的結果轉回來,這樣就也在形式上實現了行列互轉,之后的操作基本一致了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM