使用Python Pandas分析处理Airbnb数据
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()
我们可以发现共有194 行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上,欢迎评论、指正。