import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
df = pd.read_csv("租房信息.csv")
df.head()
len(df["标题"].unique()) == len(df)
# 根据标题来判断是否有重复值 false
df.drop_duplicates("标题",inplace=True)
# 删除重复值
df.index = range(len(df))
# 删除之后,重排索引,为了之后拼接数据方便
print(len(df))
# 还有2244条数据
df.info()
'''
RangeIndex: 2244 entries, 0 to 2243
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 标题 2244 non-null object
1 价格 2244 non-null float64
2 小区名 2244 non-null object
3 城市 2244 non-null object
4 街道 2244 non-null object
5 室厅数 2244 non-null object
6 面积 2244 non-null object
7 层数 2244 non-null object
8 交接人 2244 non-null object
9 详情链接 2244 non-null object
dtypes: float64(1), object(9)
memory usage: 175.4+ KB
'''
可以看到,全部数据没有缺失值,因此也不用处理缺失值。
若需要处理缺失值,可以选择:
下面给出处理确实值的具体方法:
# 查看所有缺失值数量,降序
total = df.isnull().sum().sort_values(ascending=False)
# 计算缺失值占比
percent = (df.isnull().sum()/df.isnull().count()).sort_values(ascending=False)
# 关联数量和占比
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
# 查看排名前20
missing_data.head(20)
# 根据之前计算的缺失值数据,删除对应的列
df = df.drop(missing_data[missing_data['Total'] > 1].index, 1)
# 删除某列数据缺失值对应的行
df = df.drop(df.loc[df['Electrical'].isnull()].index)
# 查看是否还有缺失值
df.isnull().sum().max()
可以看到,数据中的城市、室厅数、层数、面积,都是文本形式,因此先将数据处理一下,转换成可计算的形式:
# 将城市列分割,和数据拼接一起
df_city = pd.DataFrame(df["城市"].str.split("-").tolist(),columns=["区域","区域2"])
df = pd.concat([df,df_city],axis=1)
# 将室厅数拆解成室数和厅数,拼接在一起
df_st = pd.DataFrame(df["室厅数"].str.replace("厅","").str.split("室").tolist(),columns=["室","厅"])
df = pd.concat([df,df_st],axis=1)
# 层数数据不规范,将数据转换一下,转换为高/中/低层
df["层数2"] = df["层数"].replace(regex={"低层.*":"低层","中层.*":"中层","高层.*":"高层","共.*":"低层"})
# 面积中都带了“平方”,把它去掉
df["面积2"] = df["面积"].str[:-2].astype(float)
col = ["区域","区域2","室","厅","层数2","面积2","小区名","价格","详情链接"]
df_r = df.loc[:,col]
col2 = ["区域","区域2","室","厅","层数","面积","小区名","价格","详情链接"]
df_r.columns = col2
df_r.head()
df_r.describe()
'''
面积 价格
count 2244.000000 2244.000000
mean 96.279813 12457.483957
std 123.089698 25630.815508
min 5.000000 700.000000
25% 20.000000 2300.000000
50% 59.800000 4600.000000
75% 110.000000 12000.000000
max 1260.000000 400000.000000
'''
df_r[df_r["价格"] == 400000]
df_r[df_r["面积"] == 1260]
相当的豪华,哈哈哈,有兴趣可以点进去看看详情。
con, rbin, ax = plt.hist(df_r["价格"],bins=8, color = 'steelblue', edgecolor = 'black')
s = (rbin[1]-rbin[0])/4
for x,y in zip(rbin,con):
plt.text(x+s,y+20,'%.0f' % y,fontdict={'fontsize':11})
hi_da = []
for i in range(len(con)):
ran = f"{rbin[i]:.2f}-{rbin[i+1]:.2f}"
cnt = con[i]
hi_da.append([ran, cnt])
pd.DataFrame(hi_da, columns=["范围","计数"])
'''
范围 计数
0 700.00-50612.50 2167.0
1 50612.50-100525.00 54.0
2 100525.00-150437.50 12.0
3 150437.50-200350.00 2.0
4 200350.00-250262.50 3.0
5 250262.50-300175.00 3.0
6 300175.00-350087.50 0.0
7 350087.50-400000.00 3.0
'''
可以看到大部分租房价格都在700至50612.5之间,接下来看看面积的分布情况
con, rbin, ax = plt.hist(df_r["面积"],bins=10, color = 'steelblue', edgecolor = 'black')
s = (rbin[1]-rbin[0])/4
for x,y in zip(rbin,con):
plt.text(x+s,y+20,'%.0f' % y,fontdict={'fontsize':11})
查看一下各个范围的准确值
hi_da = []
for i in range(len(con)):
ran = f"{rbin[i]:.2f}-{rbin[i+1]:.2f}"
cnt = con[i]
hi_da.append([ran, cnt])
pd.DataFrame(hi_da, columns=["范围","计数"])
'''
范围 计数
0 5.00-130.50 1756.0
1 130.50-256.00 303.0
2 256.00-381.50 97.0
3 381.50-507.00 52.0
4 507.00-632.50 19.0
5 632.50-758.00 7.0
6 758.00-883.50 4.0
7 883.50-1009.00 3.0
8 1009.00-1134.50 1.0
9 1134.50-1260.00 2.0
'''
大部分面积都在5到130.5之间,从感觉上,面积和价格是成正比的,来看简单一下
plt.scatter(df_r['面积'], df_r['价格']);
跟我们猜测的差不多,就是有一些点,特别傲娇,没有符合我们的预期,哈哈哈。
我们猜测不至和面积有关,可能还和其他因素有关,接下来看看其他因素,对价格的影响吧。
qu = df_r["区域"].value_counts()
'''
朝阳 632
海淀 475
丰台 258
通州 183
昌平 154
大兴 118
西城 94
石景山 83
东城 74
顺义 65
房山 46
门头沟 45
密云 9
怀柔 4
北京周边 3
延庆 1
Name: 区域, dtype: int64
'''
# 在每个地区名上加一个区字,用于下面的北京地图
n = [(x+"区",int(y)) for x,y in zip(qu.index,qu.values)]
from pyecharts import options as opts
from pyecharts.charts import Map
from pyecharts.globals import CurrentConfig, NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB
map=Map(init_opts=opts.InitOpts(width='900px',height='800px'))
map.add(series_name='城市',data_pair=n,maptype='北京',is_roam=False)
map.set_global_opts(title_opts=opts.TitleOpts(title='北京租房数量'),visualmap_opts=opts.VisualMapOpts(max_=680))
map.load_javascript()
map.render_notebook()
注意load_javascript
和render_notebook
不要一起执行,不然在jupyter-lab中输出不了地图。
(地图无法上传,大家自己运行代码吧)
var = "区域"
rank_r = df_r.groupby(by=var)["价格"].median().sort_values().index
data = pd.concat([df['价格'], df[var]], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y='价格', data=data,order=rank_r)
fig.axis(ymin=0, ymax=100000);
可以看到,中位数高的前5个区域是顺义、东城、朝阳、西城和海淀,从地图上也大致看到这几个区域位于市中心。
df_r["层数"].value_counts()
'''
中层 835
低层 824
高层 585
Name: 层数, dtype: int64
'''
var = "层数"
rank_r = df_r.groupby(by=var)["价格"].median().sort_values().index
data = pd.concat([df['价格'], df[var]], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y='价格', data=data,order=rank_r)
fig.axis(ymin=0, ymax=100000);
不出所料,由于低层更方便上下楼,低层的价格更高一点,而高层的中位数大于中层的是没有想到的,不过它们也非常接近。
以上分析都是分析单独变量,只做了区域和层级对价格的影响,接下来,看看它们之前都有什么关系。
from sklearn.preprocessing import OneHotEncoder
# 初始化模型,并将数据转换
enc = OneHotEncoder()
enc.fit(df_r[["层数"]])
array_data = enc.transform(df_r[["层数"]]).toarray()
# 将数据拼接
df_ce = pd.DataFrame(array_data,columns=["中","低","高"])
df_r = pd.concat([df_r,df_ce],axis=1)
# 筛选数据,并将室厅数据转换为int类型
sn = ["区域","低","中","高","室","厅","面积","价格"]
df_rn = df_r.loc[:,sn]
df_rn.loc[:,["室","厅"]] = df_rn.loc[:,["室","厅"]].astype(int)
接下来看一下它们之间的相关性
corrmat = df_rn.corr()
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corrmat, vmax=.8, square=True);
corrmat["价格"]
'''
低 0.145677
中 -0.113269
高 -0.035243
室 0.508743
厅 0.572346
面积 0.740162
价格 1.000000
Name: 价格, dtype: float64
'''
可以看到面积和价格相关性达到了0.74,室、厅也和价格较相关,有时候可能直接的指标相关的较小,我们可以思考一下,怎么处理能让它们和价格更相关,最简单的,将它们最一些运算,来看看计算出来的指标,是否和价格更相关。
df_rn["室厅数"] = df_rn["室"]+df_rn["厅"]*2.5
df_rn.corr()["价格"]
'''
低 0.145677
中 -0.113269
高 -0.035243
室 0.508743
厅 0.572346
面积 0.740162
价格 1.000000
室厅数 0.608451
Name: 价格, dtype: float64
'''
我们可以看到,计算得出室厅数比它们单独的更相关。也就是说可以用室厅数更能解释价格。
from scipy.stats import norm
from scipy import stats
先看一下价格的偏度和峰度。
print("Skewness(偏度): %f" % df_rn["价格"].skew())
print("Kurtosis(峰度): %f" % df_rn["价格"].kurt())
'''
Skewness(偏度): 7.953787
Kurtosis(峰度): 91.264405
'''
偏度大于0,则分布偏右,即分布有一条长尾在右;峰度>0,分布陡峭;
Q-Q图也可以看数据的偏度和峰度。
sns.distplot(df_rn["价格"], fit=norm)
fig = plt.figure()
res = stats.probplot(df_rn["价格"], plot=plt)
可以看到数据的严重倾斜,下面使用对数来处理一下数据
df_rn["价格2"] = np.log(df_rn["价格"])
sns.distplot(df_rn["价格2"], fit=norm)
fig = plt.figure()
res = stats.probplot(df_rn["价格2"], plot=plt)
print("Skewness(偏度): %f" % df_rn["价格2"].skew())
print("Kurtosis(峰度): %f" % df_rn["价格2"].kurt())
'''
Skewness(偏度): 0.732321
Kurtosis(峰度): -0.092092
'''
可以看到数据偏度和峰度都得到了极大的修正。
最后再看一下修正后的价格和面积的分布情况吧
plt.scatter(df_rn['面积'], df_rn['价格2']);