数据分析Pandaspython

使用Python Pandas分析处理Airbnb数据

2019-11-25  本文已影响0人  东东隆东抢

Pandas是能够让Python成为数据分析编程语言的原因之一,它使得导入、分析和可视化数据变得更加容易。它能够快捷地读取CSVExcelJsonHtmlSQL等文件,其主要的2种数据结构是SeriesDataFrame。Series本质上是一个列,而DataFrame是一个由Series集合组成的多维表。


本文用Airbnb (爱彼迎) 的数据来学习Pandas基础知识并初步探索首都北京的民宿特点。这些数据来源于网络,官网上中国只有2个城市的数据,分别是Beijing和Hong Kong。这里采用关于北京的calendar.csv这个文件,可以通过链接进行下载。

另外本文是使用Python 3.7.4编写的,使用Jupyter Notebook构建的。在演示之前需要导入pandasmatplotlib库:

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()
我们可以发现共有194 行Priceadjusted_price列值为null,6 行minimum_nightsmaximum_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里的ft分别代表FalseTrue,即房间不可预订和可预订 。
我们来看下不可预订和可预订的比例?使用如下代码可以输出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'] 

接下来我们研究下不同时间段的订房率,将dateavailable两列数据取出生成一个新的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的列数据提供了catdtstr三种属性接口,分别对应分类数据、日期时间数据和字符串数据。
DataFrame数据中的日期时间列支持dt接口,该接口提供了dayofweekdayofyearis_leap_yearquarterweekday_name等属性和方法,DataFrame数据中的字符串列支持str接口,该接口提供了centercontainscountendswithfindextractlowersplit等大量属性和方法,大部分用法与字符串的同名方法相同,少部分与正则表达式的用法类似。

时间处理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上,欢迎评论、指正。

上一篇下一篇

猜你喜欢

热点阅读