Pandas是能夠讓Python成為數據分析編程語言的原因之一,它使得導入、分析和可視化數據變得更加容易。它能夠快捷地讀取CSV
、Excel
、Json
、Html
,SQL
等文件,其主要的2種數據結構是Series
和DataFrame
。Series本質上是一個列,而DataFrame是一個由Series集合組成的多維表。
本文用Airbnb (愛彼迎) 的數據來學習Pandas基礎知識并初步探索首都北京的民宿特點。這些數據來源于網絡,官網上中國只有2個城市的數據,分別是Beijing和Hong Kong。這里采用關于北京的
calendar.csv
這個文件,可以通過鏈接進行下載。
另外本文是使用Python 3.7.4編寫的,使用Jupyter Notebook構建的。在演示之前需要導入pandas
和matplotlib
庫:
import pandas as pd
import matplotlib.pyplot as plt
查看pandas版本,使用下面命令,是雙下劃線:
pd.__version__
我是將calendar.csv
文件與工程文件放在同一目錄下的,直接根據文件名導入CSV文件:
calendar = pd.read_csv('calendar.csv')
若不在同一目錄下,可以使用其完整路徑,路徑前的r
是為了防止一些轉義字符:
calendar = pd.read_csv(r'C:\Users\ringo\Desktop\calendar.csv')
查看前5行數據,作用等價于使用calendar.head()
,查看后5行可以使用tail()
函數:
calendar[:5]
隨機抽取5行數據
calendar.sample(5)
查看行數和列數
calendar.shape
輸出形式為元組(rows, columns)
,在這個數據表中共有 12681641行、7列,數據量之龐大。
查看索引、數據類型和內存信息
info()
提供關于數據集的基本細節,比如行和列的數量、非空值的數量、每個列中的數據類型以及DataFrame使用了多少內存。
calendar.info()
顯示所有列的數據類型
calendar.dtypes
isnull的使用
.isnull()
本身不是很有用,通常與sum()
等其他方法結合使用。
calendar.isnull().sum()
Price
和adjusted_price
列值為null,6 行minimum_nights
和maximum_nights
為null。
移除空值
數據分析經常會面臨輸入值為空的難題,這是一個需要對數據及其上下文有深入了解的決策。一般來說只建議在有少量遺漏的情況下刪除空數據。
calendar.dropna()
這個操作將刪除至少有一個空值的任何行,但是它將返回一個新的DataFrame,而不改變原來的數據。但我們也可以在這個方法中指定inplace=True
,在原有的數據表上直接進行修改。
calendar.dropna(inplace=True)
除了刪除行之外,我們還可以通過設置axis=1
來刪除空值的列。
axis=1是什么參數?
axis
從何而來,為什么需要為1才能影響列,這些都不是很明顯。查看原因,只需查看.shape
輸出 (12681441, 7)
,如上文所述這是一個元組,即12681441行和7列。注意行
在這個元組的索引0處,而列
在這個元組的索引1處。這就是為什么axis=1
會影響列的原因。
unique 和 nunique的區別?
unique()
以數組形式返回列的所有唯一值,而nunique()
是返回的是唯一值的個數。
calendar.date.unique()
calendar.date.nunique()
print('有',calendar.date.nunique() , '天' , calendar.listing_id.nunique() ,'不同的清單在calendar中')
有 383 天 34744 不同的清單在calendar中
如何獲取列?
我們可以使用方括號['列名']
的形式獲取列,當然也可以使用前者點語法。
calendar['date'] = calendar.date
type(calendar.date)
pandas.core.series.Series
這將返回一個 Series
,若要將列提取為DataFrame
,需要傳遞列名列表:
date = calendar[['date']]
type(date)
pandas.core.frame.DataFrame
如何獲取行?
一般情況下,有2種方式,根據名稱 loc
和根據index數值 iloc
。方便演示,我們先來創建一個DataFrame:
demo = pd.DataFrame({'name' :['Ringo','Jerry','Aliza','Grace','Tonny'],
'apples' :[1,3,0,3,6],
'oranges':[2,4,6,2,4]})
demo.set_index('name',inplace=True) #重新設置name列為index
這樣我們就得到如下樣式的數據表:demo.loc['Ringo']
另一方面,對于iloc
,我們給它Ringo的數值索引0:
demo.iloc[0]
我們還可以按照這樣的方式進行多行選擇:
demo.loc['Ringo':'Aliza']
demo.iloc[0:3]
注意iloc[0:3]
并不能抽取到Grace這行數據,這是因為使用.iloc
進行切片與使用列表進行切片遵循相同的規則,不包括位于末尾索引處的對象。
當然我們也可以選擇任意列,比如選擇前3行,Oranges列:
demo.loc['Ringo':'Aliza',['oranges']]
min()和max()
calendar.date.min()
calendar.date.max()
我們有2019-09-23到2020-10-09超過一年的數據。列為available
里的f
,t
分別代表False
和True
,即房間不可預訂和可預訂 。
我們來看下不可預訂和可預訂的比例?使用如下代碼可以輸出f,t對應的個數。
calendar.available.value_counts()
plt.axes(aspect='equal') # 將橫、縱坐標軸標準化處理,保證餅圖是一個正圓,否則為橢圓,等同于 plt.axis('equal')
plt.pie(calendar['available'].value_counts(),labels= ['Available','Not Available'],autopct='%.1f%%',radius = 1.2,colors= ['r','g'])
plt.title('Room available ratio') # 設置title
plt.show()
從餅圖中可以看出大概還有6成的房間可以預訂。
如果將上面的代碼修改成如下code,那么餅圖將有所改變,
explode
每一塊餅圖離開中心距離,默認值為(0,0)就是不離開中心;shadow
是否陰影,默認值為False
,即沒有陰影;textprops
設置標簽(labels)和比例文字的格式,屬于字典類型,可選參數,默認值為None
。
plt.pie(
calendar.available.value_counts(),
explode=(0,0.1), #Not Available區塊分離
labels= ['Available','Not Available'], #標簽
autopct='%1.1f%%', #顯示占比
radius = 1.2, #設置半徑
colors = ['#abcdef','#ccddaf'], #設置顏色
textprops={'fontsize':14,'color':'black'}, #設置字體、顏色
shadow = True #顯示陰影
)
plt.show()
另外可以設置成中文顯示標簽,代碼如下,自己去試試吧。
plt.rcParams['font.sans-serif']=['SimHei']
接下來我們研究下不同時間段的訂房率,將date
和available
兩列數據取出生成一個新的DataFrame
, 將其命名為new_calendar
。
new_calendar = calendar[['date','available']]
什么是apply函數?
apply()
函數形式如下:
DataFrame.apply(func, axis=0, broadcast=False, raw=False, reduce=None, args=(), **kwds)
func
函數需要自己實現,函數的傳入參數根據axis
來定,比如axis = 1
,就會把一行數據作為Series
的數據結構傳入給自己實現的函數中,我們在函數中實現對Series
不同屬性之間的計算,返回一個結果,則apply
函數會自動遍歷每一行DataFrame
的數據,最后將所有結果組合成一個Series
數據結構并返回。
新增一列available_num
,當不可預訂時將其值設為1,反之為0。
new_calendar['available_num'] = calendar['available'].apply(lambda x:1 if x =='t' else 0)
當然此處我們也可以直接使用map()
函數,效果是一樣的:
new_calendar['available_num'] = calendar['available'].map({'f':0,'t':1})
SettingWithCopyWarning
運行后發現彈出一條SettingWithCopyWarning
警告:
可以參考網上SettingwithCopyWarning 的原理和解決方案,我們可以進行
copy()
操作或者采用loc
方法。
new_calendar = calendar[['date','available']].copy()
或
new_calendar = calendar.loc[:,['date','available']]
new_calendar[:5]
什么是groupby?
groupby()
操作一般涉及拆分對象(Splitting)、應用函數(Applying)以及組合結果(Combining)的組合。它可以用于對大量數據進行分組,并在這些組上進行計算操作。如組內計數、求和、求均值以及求方差等。
Splitting
—— 通過對數據集應用一些條件將數據分組;
Combining
—— 將一個函數獨立地應用于每個組;
Combining
—— 將groupby
和結果應用到數據結構中,然后合并不同的數據集。
根據不同日期進行分組,求計算已經預定的平均值:
new_calendar = new_calendar.groupby('date')[['available_num']].mean()
new_calendar.rest_index(inplace = True)
new_calendar[:5]
使用雙括號索引[['available_num']]
是為了直接自動返回一個DataFrame對象,這樣index會直接變成date
,使用reset_index()
后又可以重新生產index,date
變成列。
什么是dt和str?
Series
對象和DataFrame
的列數據提供了cat
、dt
、str
三種屬性接口,分別對應分類數據、日期時間數據和字符串數據。
DataFrame數據中的日期時間列支持dt接口,該接口提供了dayofweek
、dayofyear
、is_leap_year
、quarter
、weekday_name
等屬性和方法,DataFrame數據中的字符串列支持str接口,該接口提供了center
、contains
、count
、endswith
、find
、extract
、lower
、split
等大量屬性和方法,大部分用法與字符串的同名方法相同,少部分與正則表達式的用法類似。
時間處理to_datetime()函數
new_calendar['date'] = pd.to_datetime(new_calendar.date,format = '%Y%m%d')
圖表顯示
plt.figure(figsize = (10 , 8))
plt.plot(new_calendar['date'] , new_calendar['available_num']*100)
plt.title('Airbnb Beijing Calendar')
plt.ylabel('Room available rate (%)')
plt.show()
圖中可見今年國慶節前后房間可預訂率明顯下降,說明很多人在國慶出游訂房。到了2020年元旦時可預訂率又直線下降。但明年4月過后為何又那么多人訂房?是春游、暑假嗎?
接下來再來看看北京哪個月的民宿較為便宜?
calendar[:5] #查看前5行
calendar[-5:] #查看后5行
發現
price
列里的數據有,
和$
符號,我們需要將其統一替換掉,另外使用info()
函數發現price
列是object
,需要將其強轉成float
類型.
calendar['price'] = calendar['price'].str.replace(',','').str.replace('$','').astype(float)
什么是strftime?
strftime函數是將字符串按照后面的格式轉換成時間元組類型。
mean_price_of_month = calendar.groupby(calendar['date'].dt.strftime('%b') , sort = False)['price'].mean()
mean_price_of_month.plot('bar',figsize=(12,7))
plt.ylabel('Average monthly price')
plt.xlabel('Month')
plt.show()
下圖可以觀察到每個月的北京的民宿平均價格差異不大,沒有明顯的淡旺季之分。最后再來看看每天的平均價格如何?
calendar['dayofweek'] =calendar.date.dt.weekday_name
weekday = calendar['dayofweek'].unique().tolist()
['Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Monday']
沒有按照周一到周日的順序,手動調整數組元素順序:
weekday = ['Monday','Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]
使用groupby
按照周幾進行分組,再對價格求平均值并進行圖表顯示。
price_of_weekday = calendar.groupby('dayofweek')['price'].mean().reindex(weekday)
plt.figure(figsize=(10,8))
plt.plot(price_of_weekday,linewidth=3, color='orange',marker= 'o',markerfacecolor='r', markersize= 8)
plt.xlabel('Day of week') #x軸坐標
plt.ylabel('Price(Yuan)') #y軸坐標
plt.title('Average Price of Weekday') #設置標題
plt.grid() #顯示網格
跟預期結果一樣,周五和周六民宿價格要稍貴一些。但是平均價格都在700+元以上,這個感覺有點不符合實際情況。最后將code放在GitHub_ Pandas_tutorial上,歡迎評論、指正。