A place for study and research

Python 100 Days Day71 Introduction to Pandas-2

|

author: jackfrued

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

Comments