Skip to content

Latest commit

 

History

History
509 lines (394 loc) · 19.3 KB

71.Pandas的应用-2.md

File metadata and controls

509 lines (394 loc) · 19.3 KB

Pandas的應用-2

DataFrame的應用

建立DataFrame物件

透過二維陣列建立DataFrame物件

程式碼:

scores = np.random.randint(60, 101, (5, 3))
courses = ['語文', '數學', '英語']
ids = [1001, 1002, 1003, 1004, 1005]
df1 = pd.DataFrame(data=scores, columns=courses, index=ids)
df1

輸出:

		語文	數學	英語
1001    69    80	79
1002    71	  60	100
1003    94    81	93
1004    88	  88	67
1005    82	  66    60
透過字典建立DataFrame物件

程式碼:

scores = {
    '語文': [62, 72, 93, 88, 93],
    '數學': [95, 65, 86, 66, 87],
    '英語': [66, 75, 82, 69, 82],
}
ids = [1001, 1002, 1003, 1004, 1005]
df2 = pd.DataFrame(data=scores, index=ids)
df2

輸出:

		語文	數學	英語
1001    69    80	79
1002    71	  60	100
1003    94    81	93
1004    88	  88	67
1005    82	  66    60
讀取 CSV 檔案建立DataFrame物件

可以透過pandas 模組的read_csv函式來讀取 CSV 檔案,read_csv函式的引數非常多,下面接受幾個比較重要的引數。

  • sep / delimiter:分隔符,預設是,
  • header:表頭(列索引)的位置,預設值是infer,用第一行的內容作為表頭(列索引)。
  • index_col:用作行索引(標籤)的列。
  • usecols:需要載入的列,可以使用序號或者列名。
  • true_values / false_values:哪些值被視為布林值True / False
  • skiprows:透過行號、索引或函式指定需要跳過的行。
  • skipfooter:要跳過的末尾行數。
  • nrows:需要讀取的行數。
  • na_values:哪些值被視為空值。

程式碼:

df3 = pd.read_csv('2018年北京積分落戶資料.csv', index_col='id')
df3

輸出:

     name   birthday    company       score
id				
1    楊x    1972-12    北京利德xxxx	  122.59
2    紀x    1974-12    北京航天xxxx	  121.25
3    王x    1974-05	  品牌聯盟xxxx    118.96
4    楊x    1975-07	  中科專利xxxx    118.21
5    張x    1974-11	  北京阿里xxxx    117.79
...  ...    ...        ...            ...
6015 孫x    1978-08	  華為海洋xxxx	  90.75
6016 劉x    1976-11	  福斯流體xxxx    90.75
6017 周x    1977-10	  贏創德固xxxx    90.75
6018 趙x	   1979-07	  澳科利耳xxxx    90.75
6019 賀x	   1981-06	  北京寶潔xxxx    90.75
6019 rows × 4 columns

說明:如果需要上面例子中的 CSV 檔案,可以透過下面的百度雲盤地址進行獲取,資料在《從零開始學資料分析》目錄中。連結:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取碼:e7b4。

讀取Excel檔案建立DataFrame物件

可以透過pandas 模組的read_excel函式來讀取 Exce l檔案,該函式與上面的read_csv非常相近,多了一個sheet_name引數來指定資料表的名稱,但是不同於 CSV 檔案,沒有sepdelimiter這樣的引數。下面的程式碼中,read_excel函式的skiprows引數是一個 Lambda 函式,透過該 Lambda 函式指定只讀取 Excel 檔案的表頭和其中10%的資料,跳過其他的資料。

程式碼:

import random

df4 = pd.read_excel(
    io='小寶劍大藥房2018年銷售資料.xlsx',
    usecols=['購藥時間', '社保卡號', '商品名稱', '銷售數量', '應收金額', '實收金額'],
    skiprows=lambda x: x > 0 and random.random() > 0.1
)
df4

說明:如果需要上面例子中的 Excel 檔案,可以透過下面的百度雲盤地址進行獲取,資料在《從零開始學資料分析》目錄中。連結:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g,提取碼:e7b4。

輸出:

    購藥時間			社保卡號	    商品名稱    銷售數量	應收金額	實收金額
0	2018-03-23 星期三	10012157328		強力xx片	 1			13.8		13.80
1	2018-07-12 星期二	108207828	    強力xx片	 1	        13.8		13.80
2	2018-01-17 星期日	13358228	    清熱xx液	 1		    28.0		28.00
3	2018-07-11 星期一	10031402228		三九xx靈	 5			149.0		130.00
4	2018-01-20 星期三	10013340328		三九xx靈	 3			84.0		73.92
...	...					...				...		...			...			...
618	2018-03-05 星期六	10066059228		開博xx通	 2			56.0		49.28
619	2018-03-22 星期二	10035514928		開博xx通	 1			28.0		25.00
620	2018-04-15 星期五	1006668328	    開博xx通	 2			56.0		50.00
621	2018-04-24 星期日	10073294128		高特xx靈	 1			5.6			5.60
622	2018-04-24 星期日	10073294128		高特xx靈	 10			56.0		56.0
623 rows × 6 columns
透過SQL從資料庫讀取資料建立DataFrame物件

pandas模組的read_sql函式可以透過 SQL 語句從資料庫中讀取資料建立DataFrame物件,該函式的第二個引數代表了需要連線的資料庫。對於 MySQL 資料庫,我們可以透過pymysqlmysqlclient來建立資料庫連線,得到一個Connection 物件,而這個物件就是read_sql函式需要的第二個引數,程式碼如下所示。

程式碼:

import pymysql

# 建立一個MySQL資料庫的連線物件
conn = pymysql.connect(
    host='47.104.31.138', port=3306,
    user='guest', password='Guest.618',
    database='hrs', charset='utf8mb4'
)
# 透過SQL從資料庫讀取資料建立DataFrame
df5 = pd.read_sql('select * from tb_emp', conn, index_col='eno')
df5

提示:執行上面的程式碼需要先安裝pymysql庫,如果尚未安裝,可以先在 Notebook 的單元格中先執行!pip install pymysql,然後再執行上面的程式碼。上面的程式碼連線的是我部署在阿里雲上的 MySQL 資料庫,公網 IP 地址:47.104.31.138,使用者名稱:guest,密碼:Guest.618,資料庫:hrs,表名:tb_emp,字符集:utf8mb4,大家可以使用這個資料庫,但是不要進行惡意的訪問。

輸出:

        ename    job     mgr      sal    comm    dno
eno						
1359	胡一刀   銷售員	3344.0   1800   200.0   30
2056	喬峰	   分析師	 7800.0   5000   1500.0	 20
3088	李莫愁	  設計師	2056.0   3500   800.0   20
3211	張無忌	  程式設計師	2056.0   3200   NaN     20
3233	丘處機	  程式設計師	2056.0   3400	NaN     20
3244	歐陽鋒	  程式設計師	3088.0   3200	NaN     20
3251	張翠山	  程式設計師	2056.0   4000	NaN     20
3344	黃蓉	   銷售主管	7800.0   3000	800.0   30
3577	楊過	   會計	  5566.0   2200   NaN	  10
3588	朱九真	  會計	 5566.0   2500   NaN	 10
4466	苗人鳳	  銷售員	3344.0   2500	NaN     30
5234	郭靖	   出納	  5566.0   2000   NaN	  10
5566	宋遠橋	  會計師	7800.0   4000   1000.0  10
7800	張三丰	  總裁	 NaN      9000   1200.0  20

基本屬性和方法

在開始講解DataFrame的屬性和方法前,我們先從之前提到的hrs資料庫中讀取三張表的資料,創建出三個DataFrame物件,程式碼如下所示。

import pymysql

conn = pymysql.connect(
    host='47.104.31.138', port=3306, 
    user='guest', password='Guest.618', 
    database='hrs', charset='utf8mb4'
)
dept_df = pd.read_sql('select * from tb_dept', conn, index_col='dno')
emp_df = pd.read_sql('select * from tb_emp', conn, index_col='eno')
emp2_df = pd.read_sql('select * from tb_emp2', conn, index_col='eno')

得到的三個DataFrame物件如下所示。

部門表(dept_df),其中dno是部門的編號,dnamedloc分別是部門的名稱和所在地。

    dname  dloc
dno
10	會計部	北京
20	研發部	成都
30	銷售部	重慶
40	運維部	天津

員工表(emp_df),其中eno是員工編號,enamejobmgrsalcommdno分別代表員工的姓名、職位、主管編號、月薪、補貼和部門編號。

        ename    job        mgr      sal     comm    dno
eno
1359	胡一刀    銷售員	   3344.0	1800	200.0	30
2056	喬峰	    分析師	    7800.0	 5000	 1500.0	 20
3088	李莫愁	   設計師	   2056.0	3500	800.0	20
3211	張無忌	   程式設計師	   2056.0	3200	NaN     20
3233	丘處機	   程式設計師	   2056.0	3400	NaN	    20
3244	歐陽鋒	   程式設計師	   3088.0	3200	NaN     20
3251	張翠山	   程式設計師	   2056.0	4000	NaN	    20
3344	黃蓉	    銷售主管   7800.0	3000	800.0	30
3577	楊過	    會計	     5566.0	  2200	  NaN	  10
3588	朱九真	   會計	    5566.0	 2500	 NaN	 10
4466	苗人鳳	   銷售員	   3344.0	2500	NaN	    30
5234	郭靖	    出納	     5566.0	  2000	  NaN	  10
5566	宋遠橋	   會計師	   7800.0	4000	1000.0	10
7800	張三丰	   總裁	    NaN      9000	 1200.0	 20

說明:在資料庫中mgrcomm兩個列的資料型別是int,但是因為有缺失值(空值),讀取到DataFrame之後,列的資料型別變成了float,因為我們通常會用float型別的NaN來表示空值。

員工表(emp2_df),跟上面的員工表結構相同,但是儲存了不同的員工資料。

        ename    job    mgr     sal      comm    dno
eno
9800	駱昊	   架構師	7800	30000	 5000	 20
9900	王小刀	  程式設計師  9800	   10000	1200	20
9700	王大錘	  程式設計師  9800    8000 	600	    20

DataFrame物件的屬性如下表所示。

屬性名 說明
at / iat 透過標籤獲取DataFrame中的單個值。
columns DataFrame物件列的索引
dtypes DataFrame物件每一列的資料型別
empty DataFrame物件是否為空
loc / iloc 透過標籤獲取DataFrame中的一組值。
ndim DataFrame物件的維度
shape DataFrame物件的形狀(行數和列數)
size DataFrame物件中元素的個數
values DataFrame物件的資料對應的二維陣列

關於DataFrame的方法,首先需要了解的是info()方法,它可以幫助我們瞭解DataFrame的相關資訊,如下所示。

程式碼:

emp_df.info()

輸出:

<class 'pandas.core.frame.DataFrame'>
Int64Index: 14 entries, 1359 to 7800
Data columns (total 6 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   ename   14 non-null     object 
 1   job     14 non-null     object 
 2   mgr     13 non-null     float64
 3   sal     14 non-null     int64  
 4   comm    6 non-null      float64
 5   dno     14 non-null     int64  
dtypes: float64(2), int64(2), object(2)
memory usage: 1.3+ KB

如果需要檢視DataFrame的頭部或尾部的資料,可以使用head()tail()方法,這兩個方法的預設引數是5,表示獲取DataFrame最前面5行或最後面5行的資料,如下所示。

emp_df.head()

輸出:

        ename    job    mgr    sal    comm  dno
eno						
1359	胡一刀   銷售員	3344   1800  200   30
2056	喬峰	   分析師	 7800   5000  1500	20
3088	李莫愁	  設計師	2056   3500  800   20
3211	張無忌	  程式設計師	2056   3200  NaN   20
3233	丘處機	  程式設計師	2056   3400	 NaN   20

獲取資料

索引和切片

如果要獲取DataFrame的某一列,例如取出上面emp_dfename列,可以使用下面的兩種方式。

emp_df.ename

或者

emp_df['ename']

執行上面的程式碼可以發現,我們獲得的是一個Series物件。事實上,DataFrame物件就是將多個Series物件組合到一起的結果。

如果要獲取DataFrame的某一行,可以使用整數索引或我們設定的索引,例如取出員工編號為2056的員工資料,程式碼如下所示。

emp_df.iloc[1]

或者

emp_df.loc[2056]

透過執行上面的程式碼我們發現,單獨取DataFrame 的某一行或某一列得到的都是Series物件。我們當然也可以透過花式索引來獲取多個行或多個列的資料,花式索引的結果仍然是一個DataFrame物件。

獲取多個列:

emp_df[['ename', 'job']]

獲取多個行:

emp_df.loc[[2056, 7800, 3344]]

如果要獲取或修改DataFrame 物件某個單元格的資料,需要同時指定行和列的索引,例如要獲取員工編號為2056的員工的職位資訊,程式碼如下所示。

emp_df['job'][2056]

或者

emp_df.loc[2056]['job']

或者

emp_df.loc[2056, 'job']

我們推薦大家使用第三種做法,因為它只做了一次索引運算。如果要將該員工的職位修改為“架構師”,可以使用下面的程式碼。

emp_df.loc[2056, 'job'] = '架構師'

當然,我們也可以透過切片操作來獲取多行多列,相信大家一定已經想到了這一點。

emp_df.loc[2056:3344]

輸出:

        ename    job        mgr      sal     comm    dno
eno
2056	喬峰	    分析師	    7800.0	 5000	 1500.0	 20
3088	李莫愁	   設計師	   2056.0	3500	800.0	20
3211	張無忌	   程式設計師	   2056.0	3200	NaN     20
3233	丘處機	   程式設計師	   2056.0	3400	NaN	    20
3244	歐陽鋒	   程式設計師	   3088.0	3200	NaN     20
3251	張翠山	   程式設計師	   2056.0	4000	NaN	    20
3344	黃蓉	    銷售主管   7800.0	3000	800.0	30
資料篩選

上面我們提到了花式索引,相信大家已經聯想到了布林索引。跟ndarraySeries一樣,我們可以透過布林索引對DataFrame物件進行資料篩選,例如我們要從emp_df中篩選出月薪超過3500的員工,程式碼如下所示。

emp_df[emp_df.sal > 3500]

輸出:

        ename    job        mgr      sal     comm    dno
eno
2056	喬峰	    分析師	    7800.0	 5000	 1500.0	 20
3251	張翠山	   程式設計師	   2056.0	4000	NaN	    20
5566	宋遠橋	   會計師	   7800.0	4000	1000.0	10
7800	張三丰	   總裁	    NaN      9000	 1200.0	 20

當然,我們也可以組合多個條件來進行資料篩選,例如從emp_df中篩選出月薪超過3500且部門編號為20的員工,程式碼如下所示。

emp_df[(emp_df.sal > 3500) & (emp_df.dno == 20)]

輸出:

        ename    job        mgr      sal     comm    dno
eno
2056	喬峰	    分析師	    7800.0	 5000	 1500.0	 20
3251	張翠山	   程式設計師	   2056.0	4000	NaN	    20
7800	張三丰	   總裁	    NaN      9000	 1200.0	 20

除了使用布林索引,DataFrame物件的query方法也可以實現資料篩選,query方法的引數是一個字串,它代表了篩選資料使用的表示式,而且更符合 Python 程式設計師的使用習慣。下面我們使用query方法將上面的效果重新實現一遍,程式碼如下所示。

emp_df.query('sal > 3500 and dno == 20')

重塑資料

有的時候,我們做資料分析需要的原始資料可能並不是來自一個地方,就像上面的例子中,我們從關係型資料庫中讀取了三張表,得到了三個DataFrame物件,但實際工作可能需要我們把他們的資料整合到一起。例如:emp_dfemp2_df其實都是員工的資料,而且資料結構完全一致,我們可以使用pandas提供的concat函式實現兩個或多個DataFrame的資料拼接,程式碼如下所示。

all_emp_df = pd.concat([emp_df, emp2_df])

輸出:

        ename    job        mgr      sal     comm    dno
eno
1359    胡一刀    銷售員	   3344.0	1800	200.0	30
2056    喬峰	    分析師	    7800.0	 5000	 1500.0	 20
3088    李莫愁	   設計師	   2056.0	3500	800.0	20
3211    張無忌	   程式設計師	   2056.0	3200	NaN     20
3233    丘處機	   程式設計師	   2056.0	3400	NaN	    20
3244    歐陽鋒	   程式設計師	   3088.0	3200	NaN     20
3251    張翠山	   程式設計師	   2056.0	4000	NaN	    20
3344    黃蓉	    銷售主管   7800.0	3000	800.0	30
3577    楊過	    會計	     5566.0	  2200	  NaN	  10
3588    朱九真	   會計	    5566.0	 2500	 NaN	 10
4466    苗人鳳	   銷售員	   3344.0	2500	NaN	    30
5234    郭靖	    出納	     5566.0	  2000	  NaN	  10
5566    宋遠橋	   會計師	   7800.0	4000	1000.0	10
7800    張三丰	   總裁	    NaN      9000	 1200.0	 20
9800    駱昊	    架構師     7800.0	 30000	 5000.0	 20
9900    王小刀	   程式設計師     9800.0	10000	1200.0	20
9700    王大錘	   程式設計師     9800.0	8000	600.0	20

上面的程式碼將兩個代表員工資料的DataFrame拼接到了一起,接下來我們使用merge函式將員工表和部門表的資料合併到一張表中,程式碼如下所示。

先使用reset_index方法重新設定all_emp_df的索引,這樣eno 不再是索引而是一個普通列,reset_index方法的inplace引數設定為True表示,重置索引的操作直接在all_emp_df上執行,而不是返回修改後的新物件。

all_emp_df.reset_index(inplace=True)

透過merge函式合併資料,當然,也可以呼叫DataFrame物件的merge方法來達到同樣的效果。

pd.merge(dept_df, all_emp_df, how='inner', on='dno')

輸出:

    dno dname  dloc eno   ename  job      mgr     sal    comm
0   10	會計部	北京	3577  楊過	會計	   5566.0  2200   NaN
1   10	會計部	北京	3588  朱九真  會計     5566.0  2500   NaN
2   10	會計部	北京	5234  郭靖	出納	   5566.0  2000   NaN
3   10	會計部	北京	5566  宋遠橋  會計師   7800.0	 4000   1000.0
4   20	研發部	成都	2056  喬峰	架構師   7800.0  5000	 1500.0
5   20	研發部	成都	3088  李莫愁  設計師   2056.0	 3500   800.0
6   20	研發部	成都	3211  張無忌  程式設計師   2056.0	 3200   NaN
7   20	研發部	成都	3233  丘處機  程式設計師   2056.0	 3400   NaN
8   20	研發部	成都	3244  歐陽鋒  程式設計師   3088.0	 3200   NaN
9   20	研發部	成都	3251  張翠山  程式設計師   2056.0	 4000   NaN
10  20	研發部	成都	7800  張三丰  總裁     NaN     9000   1200.0
11  20	研發部	成都	9800  駱昊    架構師   7800.0  30000	 5000.0
12  20	研發部	成都	9900  王小刀  程式設計師	 9800.0	 10000  1200.0
13  20	研發部	成都	9700  王大錘  程式設計師	 9800.0	 8000   600.0
14  30	銷售部	重慶	1359  胡一刀  銷售員	 3344.0	 1800   200.0
15  30	銷售部	重慶	3344  黃蓉    銷售主管 7800.0	 3000   800.0
16  30	銷售部	重慶	4466  苗人鳳  銷售員   3344.0	 2500   NaN

merge函式的一個引數代表合併的左表、第二個引數代表合併的右表,有SQL程式設計經驗的同學對這兩個詞是不是感覺到非常親切。正如大家猜想的那樣,DataFrame物件的合併跟資料庫中的表連線非常類似,所以上面程式碼中的how代表了合併兩張表的方式,有leftrightinnerouter四個選項;而on則代表了基於哪個列實現表的合併,相當於 SQL 表連線中的連表條件,如果左右兩表對應的列列名不同,可以用left_onright_on引數取代on引數分別進行指定。

如果對上面的程式碼稍作修改,將how引數修改為left,大家可以思考一下程式碼執行的結果。

pd.merge(dept_df, all_emp_df, how='left', on='dno')

執行結果比之前的輸出多出瞭如下所示的一行,這是因為left代表左外連線,也就意味著左表dept_df中的資料會被完整的查出來,但是在all_emp_df中又沒有編號為40 部門的員工,所以對應的位置都被填入了空值。

17  40  運維部  天津  NaN  NaN  NaN  NaN  NaN  NaN