【数据分析案例】某瓣读书数据分析

前言说明

本文借用原文作者路人甲的爬取数据进行数据分析思路分享,如有侵犯相关版权信息,请联系本文作者删除文章

数据来源

数据源来自路人甲的公众号,可关注「路人甲 TM」公众号获取数据源

部分数据特征为方便计算,有所改动(替换、删减)

数据说明

  1. 数据源为某瓣读书中爬取特定时间范围内的书单信息
  2. 因数据源未经过处理,存在部分数据丢失和格式错乱,经初步处理后,数据源仍保留重复数据
  3. 因数据源存在同名书或某本书多次更新版本的情况,以ISBM为“唯一指标”
  4. 数据特征分别为:书名、作者、出版社、出版时间、数(页数)、价格、ISBM(书籍ID)、评分
  5. 部分数据特征会存在重复、缺漏、丢失、格式错乱的情况,下文将根据指定条件清洗数据
  6. 数据源各类数据特征的极值均有不实,分类分析时,需注意其极值是否为异常值

分析思路:

  1. 基础设置(导入模块、格式设置、数据整理预览)
  2. 数据清洗(查重、缺失值、异常值)
  3. 数据整体情况分析
  4. 数据分析
  5. 总结

基础设置

导入数据源


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns


data = pd.read_csv(\book_douban.csv',header=0)    #文件地址根据自己保存位置调整

调整格式


pd.set_option('expand_frame_repr', False)  # 当列太多时显示不清楚
pd.set_option('display.unicode.east_asian_width', True)  # 设置输出右对齐

查看列名,观察是否有文件隐藏列或未命名列

通过返回的信息发现最后一列为未命名列,并查看源文件发现为无用的数据信息,选择直接删除该列,并重新查看数据分布


print(data.columns)
data = data.iloc[:,:-1]
print(data.info())

可知,该数据源文件的数据结构为60671行9列

部分特征存在丢失,其中“作者”列丢失3个,“价格”列丢失15个

除评分为浮点数数据类型外,其余皆为对象类型,既“出版时间”和“数”、‘ISBM’列存在格式错误的信息


数据清洗

先观察每个数据特征的情况,再逐一处理


def distribution(column):
    x = data[column].value_counts().head(3).index
    y = data[column].value_counts().head(3).values
    df1 = pd.DataFrame(y,index=x).transpose()       # 行列转置

    sns.barplot(data=df1)
    plt.title('图%d:%s'%(i+1,column))                # 设置标题
    plt.subplots_adjust(wspace=0.5,hspace=0.5)       # 设置图形间隔


for i in range(9):
    plt.subplot(3,3,i+1)
    distribution(data.columns[i])

plt.show()

图1,“点击上传封面图片”数量最多且为错误字段,以删除该行信息的方式处理

图2,存在3个空值,以删除该行信息的方式处理

图2、3、6、7、8,缺失值显示为“None”字符串,也以删除该行信息的方式处理

图4,存在多种时间格式且错乱字符串,本文仅为提供分析思路,因此对源数据进行更改,以“2001年至2010年”时段内重新随机抽取一日期进行替换

图5,缺失页数的信息统一默认删除处理

图6,数据显示的价格单位不一,为方便统计全部去除价格符号并默认以人民币计算

图8,0.0为正常数据范围,不作处理

图9,“None”值为正常数据范围,但上文中显示‘评论数量’列的数据类型为object,既存在格式错误,将数据类型不为数值的数据改为0


针对观察提出的问题,逐一对各特征进行数据清洗时,发现存在未记录的问题

‘书名’字段实际存在ISBM值为‘9787508652825’,书名显示错误的书籍(通过excel发现),但因数量只有一个,且确实存在以日期格式命名的书籍,这里就不做批量(条件筛选)和单独处理了

‘出版社’字段,存在同一家出版社但多种字符描述的现象,同时,也存在非出版社名称的字符串,因没有出版社详细清单,难以筛选和重命名,默认皆为不同出版社处理

‘数’和‘价格’字段,存在‘None’字符串的同时也存在非数字描述的字符串,以筛选是否可转换为数值作为判断,进行区分删除


data_clear_1 = data.drop(data[data['书名'] == '点击上传封面图片'].index)  # 清洗错乱的书名

data_clear_2 = data_clear_1.dropna(subset=['作者'])  # 清除含有空值的信息

drop_col_list = ['作者', '出版社', '价格', 'ISBM', '数']  # 清除带有"None"字符串的信息
for i in drop_col_list:
    data_clear_2 = data_clear_2.drop(data_clear_2.loc[data_clear_2[i] == 'None'].index).reset_index(drop=True)

date_list = pd.date_range('1/1/2001', '12/31/2010')  # 重新配置时间并调整时间格式
for j in range(len(data_clear_2)):
    data_clear_2.iloc[j, 3] = random.choice(date_list)

data_clear_2.replace('None', 0, inplace=True)

data_clear_2['评论数量'] = data_clear_2['评论数量'].astype('int')       # 转换为数值类型

# 筛选无法转换为数值的字符串进行删除
data_clear_2['数'] = data_clear_2['数'].apply(pd.to_numeric, errors='coerce').fillna(0.0).astype('int')
data_clear_2 = data_clear_2.drop(data_clear_2.loc[data_clear_2['数'] == 0].index).reset_index(drop=True)

str_list = []
for i in range(len(data_clear_2['价格'])):
    if pd.to_numeric(data_clear_2.iloc[i, 5], errors='ignore'):  # 判断转换是否可以转换为数值类型
        data_clear_2.iloc[i, 5] = pd.to_numeric(data_clear_2.iloc[i, 5], errors='ignore')  # 保留可转换的字符串
    else:
        str_list.append(i)  # 不可转换的字符串行索引列表

data_clear_3 = data_clear_2.drop(str_list).reset_index(drop=True)       #删除后,重置索引
data_clear_3 = data_clear_3.iloc[1:]  # 特殊字符串
data_clear_3['价格'] = data_clear_3['价格'].apply(pd.to_numeric, errors='coerce').fillna(0.0).astype('float')

data_clear_3['出版时间'] = pd.to_datetime(data_clear_3['出版时间']).dt.floor('d')

print(data_clear_3.info())
print('-' * 100)
print(data_clear_3.describe())

数据整体情况分析

经以上问题处理后,数据明细如下

数据量由60671缩减到51770,共删除8901行数据

参与分类计算的‘出版时间’、‘数’、‘价格’、‘评论数量’均转换为整数或浮点数的数据类型

但仔细观察极值,结合主观常识判断,数据仍存在‘错误’的现象,后续将各个数据特征进行分类可视化,观察是否存在异常值


书名

首先为‘书名’列,以‘书名’列为分类并统计‘作者’数量,从上图可知,同一书名存在多个作者或同作者多列信息


data_col_1_bar = data_clear_3.groupby('书名').agg({'作者':'count'}).sort_values(by='作者',ascending=False).head().reset_index()
g = sns.barplot(x='书名',y='作者',data=data_col_1_bar)
for i,row in data_col_1_bar.iterrows():
    g.text(row.name,row.作者,row.作者,ha='center',c='black')

筛选数量最多的前5个书单的作者,观察发现皆为同一作者,仅字符表达方式不一


data_col_1_name_max = data_clear_3[data_clear_3['书名'].isin(data_col_1_bar['书名'].values)].sort_values(by='书名').reset_index(drop=True).iloc[:,:2]
print(data_col_1_name_max)

但由于实际情况存在两本书相同的名字,以‘书名’作为分类统计并无实际意义


作者

若直接统计作者数量,也会因为‘书名’的问题导致部分数据偏高

为避免该类情况,以‘书名’作为唯一字段,删除重复数据,保留第一行数据

注:该处理方式会将同名书籍也一并删除,因此分析将以‘无同名书籍’作为假设条件


data_drop_dup_bookname = data_clear_3.drop_duplicates(subset='书名', keep='first').reset_index(drop=True)      # 删除重复书名

print(data_drop_dup_bookname.nunique())

查看删除重复书名后,统计各字段非重复数据

观察上图数据,‘书名’有49333行数据,但‘作者’仅有34412行数据;由此可知,数据源中有部分书籍为同一作者

按作者分类,统计前5个作者发行的书籍,再将前5个作者的最高评分书籍列出


data_author = data_drop_dup_bookname.groupby('作者').agg({'书名': 'count'})\
    .sort_values('书名',ascending=False).head(5).reset_index()        # 按作者分类,统计总数
g2 = sns.barplot(x='作者', y='书名', data=data_col_2_bar)
for i, row in data_col_2_bar.iterrows():
    g2.text(row.name, row.书名, row.书名, ha='center', c='black')

书籍数量最多的作者为‘亦舒’,其次为‘鲁迅’、‘王小波’、‘古龙’、‘郑渊洁’


print(data_col_2[data_col_2['作者']== '亦舒'].sort_values(by='评分',ascending=False).head().reset_index(drop=True))
print('-'*100)
print(data_col_2[data_col_2['作者'] == '鲁迅'].sort_values(by='评分',ascending=False).head().reset_index(drop=True))

而在‘亦舒’的书籍中,评分最高的为《紅樓夢裏人》,评分8.6分

‘鲁迅’评分最高的为《鲁迅全集》,评分9.5分

......


出版社

同样以‘无同名书籍’作为假设条件,按出版社数量,分类统计前5个出版社发行的书籍


data_publisher = data_drop_dup_bookname.groupby('出版社').agg({'书名': 'count'})\
    .sort_values('书名',ascending=False).head().reset_index()        # 按出版社分类,统计总数
print(data_publisher)
g3 = sns.barplot(x='出版社', y='书名', data=data_publisher)
for i, row in data_publisher.iterrows():
    g3.text(row.name, row.书名, row.书名, ha='center', c='black')

发行出版社最高为‘中信出版社’,共发行1463本书籍,其中《行动的勇气》评分高达9.9分,而

《货币战争》有37909条评论数量,是该出版社讨论最多的书籍

其次发行最高的为‘人民文学出版社’,共发行972本书记,《戚蓼生序本石头记》评分10分,《围城》有178288条评论数量,是该出版社讨论最多的书籍

....


for i in data_publisher['出版社']:
    print(data_drop_dup_bookname[data_drop_dup_bookname['出版社']== i].
          sort_values(by='评分',ascending=False).head(1).reset_index(drop=True))
    print('-' * 100)

因‘出版社’字段存在‘名称描述’、‘名称错误’等问题,最低出版社发行数无统计意义,在此不作过多分析


出版时间

按年份、月份分类统计年月的发行数量

注:由于时间已被替换为随机抽取2001-1-1至2010-12-31之间的日期,因此每次执行代码时间都将随机改变

以上图为观察图例,发行书籍数量最高的是2001年(5079本),最低为2004年(4858年),两者差值为221本

从2001年至2007年,数据呈规律型显著浮动,2007年后发行数量逐渐缓慢上升

注:由于书籍数据由34412个国内外作者所组成,影响数据的浮动原因并非单一作者数据所造成,需考虑外部因素的影响。

但也因为出版时间是随机数据替换而来,无法对照2007年的相关政策或事件;因此,仅对数据表面分析,并无结合实际发行情况作2007年前后数据变动的原因分析

书籍在1月、5月、8月、10月发行数量最高,在二月最低(3800本),其余处于正常浮动中(设中线20%为浮动上下限)

再根据各月的书籍评分统计其均值,发现,实际2月的发行书籍质量并无随数量下降,且各月的评分均值均处于7.2-7.35之间


print(data_drop_dup_bookname.groupby('month').agg({'评分': 'mean'}).reset_index())

结合年份图和月份图可知,书籍发行数据大部分处于环比上下明显浮动的现象,但其中有小部分月份数据浮动显著

可追查这几个月份的作者数量、评分、评论数量,观察除数量有异常波动外是否还影响其书籍质量



data_page = data_drop_dup_bookname.groupby('数').agg({'书名': 'count'})\
    .sort_values(by='书名').reset_index().rename(columns={'书名':'nums'})
print(data_page.describe())

从上图我们可以看出,51770本书中,最小页数为‘1’,导出页数为‘1’的书名进行抽取查询后,发现部分书籍实际页数并非为‘1’,经查询导致该类现象的原因主要为:某瓣读书的页数可由用户随意更改

再次筛选页数小于25的书籍清单,发现仅有77本

数据描述显示,一共有1749种页数,50%的页数在441-1548页之间,其中值得关注的是‘最大页数’240000,书名为《希利尔讲世界地理》,经某瓣读书查询,实际页数为276页


print(data_clear_3[data_clear_3['数'] == 240000])

因此,该数据特征也存在错乱现象,为此需将最大和最小前10的页数导出,观察是否有误

手动查验前4个ISBM值,发现只有两个是页数异常


data_page_head = data_page.head(10).reset_index(drop=True)
print(data_page_head)
for i in data_page_head['数']:
    print(data_drop_dup_bookname[data_drop_dup_bookname['数'] == i][['书名','ISBM']])
print('-'*100)

再看最小页数时,发现有部分书籍页数与数据源一致,也有部分页数错乱


data_page_tail = data_page.tail(10).reset_index(drop=True)
print(data_page_tail)
for i in data_page_tail['数']:
    print(data_drop_dup_bookname[data_drop_dup_bookname['数'] == i][['书名','ISBM','数']])
print('-'*100)

页数一致:可能为人为恶意录入,并非真实页数

页数不一致:数据源错乱,可能为数据爬取时,未能正确爬取


价格

因书籍可分类为纸质书籍和电子书籍,两者的价格范围不一

通过对比电商价格范围,最终将0.5元设为纸质书籍最低价格,剩下48219行,共删除1114行数据


print(len(data_drop_dup_bookname))
data_price = data_drop_dup_bookname.drop(data_drop_dup_bookname[data_drop_dup_bookname['价格'] < 0.5].index)
print(len(data_drop_dup_bookname))

data_price = data_price.groupby('价格').agg({'书名': 'count'})\
    .sort_values(by='价格',ascending=False).reset_index().rename(columns={'书名':'nums'})
print(data_price.describe())

书籍价格均值为127.88元,而中位数为45.7,标准差195.23,;其主要原因是因为价格极差过大

价格最高值:999元,为书籍《同志性愛聖經》,评分7.5分


print(data_drop_dup_bookname[data_drop_dup_bookname['价格'] == 999])

价格数量排名前5个价格处于25-38之间


data_price_nums = data_drop_dup_bookname.groupby('价格').agg({'书名':'count'})\
    .sort_values(by='书名',ascending=False).head().reset_index().rename(columns={'书名':'nums'})
g6 = sns.barplot(x='价格',y='nums',data=data_price_nums,order=data_price_nums['价格'].values.astype('int'))
for i,row in data_price_nums.iterrows():
    g6.text(row.name,row.nums+1,row.nums,ha='center',c='black')

观察上文describe后显示,书籍价格的四分位距处于18.2-138元之间

现将价格以十分位数的形式展现,结合价格数量排名前五的价格区间,得知大部分书籍的定价处于(22.62-45.7)元之间

扩散分析思路

  • 联动发行书籍最多的作者(亦舒),对比其书籍价格处于哪个百分比之间?

  • 联动发行书籍最多的出版社(中信出版社),对比其书籍价格处于哪个百分比之间?

  • 统计大部分书籍(22.62-45.7元)的页数处于哪个范围?


ISBM

通过统计非重复个数发现,有两个ISBM值为相同


print(len(data_drop_dup_bookname))
print(data_drop_dup_bookname['ISBM'].nunique())

通过定位重复值,发现重复ISBM值的书籍存在两个未知问题


data_duplicated = data_drop_dup_bookname['ISBM'].duplicated(keep=False)
print(data_drop_dup_bookname[data_duplicated == True])

第一个未知问题:ISBM值相同,实际书籍相同

ISBM值为“9787539924557”的书籍实际为同一本书籍,但名称不相同

导致该现象可能是:某瓣读书数据库添加书籍信息时,未查询是否已存在该书籍

第二个未知问题:ISBM值相同,实际书籍不相同

ISBM值为“9787500673101”的书籍,实际为《超级漫画素描技法》的丛书系列书籍

但通过抽查该丛书系列的其他书籍时,发现ISBM值并非为“9787500673101”

既,ISBM值“9787500673101”只有上图两本书籍,且两本书籍并非为同一本书

通过上述发现,得知ISBM值并非为唯一值,存在特殊情况


评分

因在数据清洗过程中,将“None”字符串替换为0.0,因此需将评分为0.0的数据删除


data_grade = data_drop_dup_bookname.drop(data_drop_dup_bookname[data_drop_dup_bookname['评分'] == 0.0].index)
print(data_grade['评分'].describe())

数据剩余44713行,共删除4620行数据,评分均值为8.0分

最低2.5分,共一本


print(data_grade[data_grade['评分'] == 2.5])

最高10.0分,共14本


print(data_grade[data_grade['评分'] == 10.0])

按评分分类统计数量前十的分数

从下图得知,前十最高评分数量的分值在7.6-8.5之间,其中8.1分数量最多(2353个),依次为8.2分、8.4分、7.8分......


data_grade_g = data_grade.groupby('评分').agg({'书名':'count'})\
    .sort_values(by='书名',ascending=False).head(10).reset_index().rename(columns={'书名':'nums'})
g8 = sns.barplot(x='评分',y='nums',data=data_grade_g,order=data_grade_g['评分'].values)
for i,row in data_grade_g.iterrows():
    g8.text(row.name,row.nums+1,row.nums,ha='center',c='black')

利用‘直接舍去取整’的方法对评分进行分层计算

在2.0-2.9评分之间的书籍只有3本

3.0-3.9评分之间的书籍只有2本

9.0-10.0评分之间的书籍只有3935本

scale为7和8的占比最高,与上文“前十最高评分数量的分值区间”结论吻合

......


data_grade['scale'] = pd.cut(data_grade['评分'],
                             bins=[i for i in range(2,11)],
                             labels=range(2,10))
data_grade_s = data_grade.groupby('scale').agg({'价格':'count'}).reset_index().rename(columns={'价格':'nums'})
print(data_grade_s)

扩散分析思路

  • 发行书籍最多的作者(亦舒),其书籍评分是否有7.1-9.0评分区间以外的书籍?

  • 发行书籍最多的前5个出版社,在7.1-9.0评分区间以外的书籍有多少,各占比多少?

  • 统计评分在7.1-9.0之间的书籍页数分布如何?均值是多少?

  • 统计评分在7.1-9.0之间的书籍,其价格分布如何?在22.62-45.7元之间有多少?占比多少?


评论数量

评论数量分类4193行,评论数量最高值为178288,评论数量最低值为0(占比最高),极差偏大

导出评论数量最高的5本书籍的关键信息,发现作者东野圭吾占了2本


data_comment_h = data_drop_dup_bookname.groupby('评论数量').agg({'书名':'count'}).sort_values(by='评论数量',ascending=False).reset_index().rename(columns={'书名':'nums'})
for i in data_comment_h['评论数量'].head():
    print(data_drop_dup_bookname[data_drop_dup_bookname['评论数量'] == i][['书名','作者','评分','价格','评论数量','ISBM']])
    print('-' * 100)

针对‘评论数量’特征进行describe统计,由于评论数量为0无统计意义,因此剔除数量为0后再统计


data_comment = data_drop_dup_bookname.groupby('评论数量').agg({'书名':'count'})\
    .sort_values(by='书名',ascending=False).reset_index().rename(columns={'书名':'nums'})
data_comment = data_comment.drop(data_comment[data_comment['评论数量'] == 0].index).reset_index(drop=True)
print(data_comment.describe())

数量均值为5727条,但由于标准差为11881,因此数量均值并不可靠

max=33.55倍Q3

Q3=2.25倍median

median=2.22倍Q2

Q2=106倍min

......

极值与四分位距差距过大,反映出部分书籍的受关注程度较高(评论数量高)

再根据nums次数统计,将次数最多的前10个评论数量可视化展现

通过上图观察,次数最多的评论数量在10-20个之间

其中,书籍评论个数为‘10’的次数高达509次,占总书籍(49333本)1.03%


data_comment = data_comment.head(10)
g9 = sns.barplot(x='评论数量',y='nums',data=data_comment,order=data_comment['评论数量'].values)
for i,row in data_comment.iterrows():
    g9.text(row.name,row.nums+1,row.nums,ha='center',c='black')
plt.show()

扩散分析思路

  • 发行书籍最多的前5名作者,其书籍评论数量均值是多少?评论数量最高的书籍是哪本?

  • 统计发行书籍最高的前5个出版社,在评分7.1以上,评论数量最高的前10本书籍清单;价格最高的钱10本书籍,评论数量各是多少?

  • 统计评论数量在10-20个之间的书籍页数分布如何?均值是多少?

  • 统计评分在7.1-9.0之间、评论数量在10-20个之间的书籍,其价格分布如何?在22.62-45.7元之间有多少?占比多少?

  • 查看评论数量前5的4位作者最高评分、价格、页数的书籍清单


数据整体情况分析总结

  • 书名:共51770行数据,数据源中同一书名存在多个作者或同作者多列信息

  • 作者:共34412个作者,数据源中有部分书籍为同一作者,书籍数量最多的作者为‘亦舒’‘而她的书籍中,评分最高的为《紅樓夢裏人》,评分8.6分

  • 出版社:共4003个出版社,书籍数量最多的出版社为‘中信出版社’

  • 出版时间:年份发行数量最多的为‘2007年’,月份发行数量最多的为‘2月’,但各月评分均值并无明显差异

  • 数:共1749种页数,50%的页数在441-1548页之间,存在部分数据错乱的现象

  • 价格:共48219行数据,1232个价位,极差偏大,大部分书籍的定价处于(22.62-45.7)元之间

  • ISBM:非唯一值,存在‘ISBM值相同,实际书籍相同’和‘ISBM值相同,实际书籍不相同’两种特殊情况

  • 评分:共44713行有效评分数据,大部分评分集中在7.1-9.0之间

  • 评论数量:共4192类,极差偏大,大部分书籍的评论书籍集中在(10-20)个


数据分析

有哪些评分和作品数量都很高的作者

思路:

以‘评分取均值,数量取最高值’来定义‘评分和作品数量都很高’

新增‘作品数量’字段后连接到数据中

按评分和作品数量两个字段来排名

通过先以‘评分’来排名后,观察到靠前20位作者的‘作品数量’皆为1

所以下面以‘作品数量’来排名,再截取前20名作者


author_cnt_nums = data_clear_3.groupby('作者').agg({'书名':'count','评分':'mean'}).sort_values(by='书名',ascending=False).reset_index().rename(columns={'书名':'nums'})
print(author_cnt_nums.head(20)['作者'])

再提取20位作者评分最高的书籍,考虑到有评分相同的情况,增加评论数量字段来排名


author_top20 = data_clear_3[data_clear_3['作者'].isin(author_cnt_nums.head(20)['作者'])].sort_values(by=['作者','评分','评论数量'],ascending=False)
author_top20_bookname = author_top20.groupby('作者').head(1).reset_index(drop=True)
print(author_top20)
print('-'*100)
print(author_top20_bookname)

如果再结合整体分析中‘评分’的区间占比,以8.6分为划分线,则只剩下14本书籍


print(author_top20_bookname[author_top20_bookname['评分'] > 8.6]['书名'].reset_index(drop=True))

注:这只是基于表面的数据进行排名比较,实际深入挖掘就不是这么玩的

比如有些书籍实际是个别学术领域(如画画)且为丛书系列,或者作者的书籍很多,但是书籍评分皆与均值差异不大,导致出现表面的‘评分和作品数量’都很高的情况

排名不一定限定为前20名,也可以是前百名...


书籍的评分和评论数量之间是否存在相关性

删除‘数’小于25的数据行

删除‘价格’小于0.5的数据行

删除‘评分’为0.00的数据行

删除‘评论数量’为0的数据行


data_corr = data_clear_3.drop(data_clear_3.loc[(data_clear_3 == 0).any(1)].index)
data_corr = data_corr.drop(data_corr[data_corr['数'] <25].index)
data_corr = data_corr.drop(data_corr[data_corr['价格'] <0.5].index)
print(data_corr.describe())

按照上述条件删除部分数据后,数据行由51770行缩减到46030行


print(data_corr.corr().round(2))

r=0.02,以0.2为区间划分,则在[-0.2,2]之间,‘评分’与‘评论数量’之间的相关性属于无相关


sns.scatterplot(x='评论数量',y='评分',data=data_corr)

哪些书籍值得推荐

作者为发行书籍数前10000名

出版社为发行书籍数前1000名

价格高于总体价格30%(22.62元)

评分高于7.1分

评论数量不低于10个

按评分高低,筛选前百书籍


rec_1 = data_drop_dup_bookname.groupby('作者').agg({'书名':'count'}).sort_values('书名',ascending=False).head(10000).reset_index()['作者']
rec_2 = data_drop_dup_bookname.groupby('出版社').agg({'书名':'count'}).sort_values('书名',ascending=False).head(1000).reset_index()['出版社']
recomment_book = data_drop_dup_bookname.loc[(data_drop_dup_bookname['作者'].isin(rec_1)) &
                                            (data_drop_dup_bookname['出版社'].isin(rec_2)) &
                                            (data_drop_dup_bookname['价格'] > 22.6) &
                                            (data_drop_dup_bookname['评分'] > 7.1) &
                                            (data_drop_dup_bookname['评论数量'] > 10)]
print(recomment_book.sort_values(by='评分',ascending=False).reset_index(drop=True).head(100).iloc[:,:-3])

还是那样,没有分类的话,百大推荐书籍中会存在部分专业领域的书籍


总结

‘出版时间’列也可尝试调整格式的方面进行清洗,作者只是为了提供分析思路而改变数据;

上文中提到的各种限定条件也是根据数据来划分而已,实际需要结合业务场景来划分才会更有说服力;

由于数据缺失严重、同书籍重复出现、无唯一指标,书籍信息失真等等...,关键‘时间’字段也是重新调整过,因此本文也只做的针对该数据源的描述性分析;

作者只对临时想到的分析点进行实操,读者也可根据自身理解调整分析问题点;

如对上述分析过程或有更好的建议,欢迎指出批评

再次感谢原文作者路人甲的提供的爬取数据!!!


历史文章

【数据分析案例】pandas + matplotlib 人货场 + RFM + 用户复购分析 电商水果销售

【数据分析案例】基于 Pandas 分析天池大赛 - 淘宝用户行为数据推荐

【数据分析案例】游戏付费用户 RFM 分析案例


完整代码


import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import random

# 基础设置
data = pd.read_csv(r'C:\Users\x\Desktop\douban\book_douban.csv')

# 调整格式
pd.set_option('expand_frame_repr', False)  # 当列太多时显示不清楚
pd.set_option('display.unicode.east_asian_width', True)  # 设置输出右对齐
plt.figure(figsize=(20, 10), dpi=200)
# 中文设置
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用于显示中文
plt.rcParams['axes.unicode_minus'] = False  # 用于解决保存图像是负号‘-’显示为方框的问题
# 绘图风格
sns.set_style('darkgrid', {'font.sans-serif': ['SimHei']})
sns.color_palette('colorblind')

# 删除多余列
# print(data.columns)
data = data.iloc[:, :-1]


# 数据结构
# print(data.info())

#  图形设置
def Multiple_figures(data):
    for index, col in enumerate(data.columns):
        plt.subplot(3, 3, index + 1)
        x = data[col].value_counts().head(3).index
        y = data[col].value_counts().head(3).values
        sns.barplot(x=x, y=y)

        plt.title('图%d:%s' % (index + 1, col), fontsize=10)  # 设置标题
        plt.subplots_adjust(wspace=0.5, hspace=0.5)  # 设置图形间隔
        plt.xticks(fontsize=6)


# 数据清洗
data_clear_1 = data.drop(data[data['书名'] == '点击上传封面图片'].index)  # 清洗错乱的书名
data_clear_2 = data_clear_1.dropna(subset=['作者'])  # 清除含有空值的信息
drop_col_list = ['作者', '出版社', '价格', 'ISBM', '数']  # 清除带有"None"字符串的信息
for i in drop_col_list:
    data_clear_2 = data_clear_2.drop(data_clear_2.loc[data_clear_2[i] == 'None'].index).reset_index(drop=True)

date_list = pd.date_range('1/1/2001', '12/31/2010')  # 重新配置时间并调整时间格式
for j in range(len(data_clear_2)):
    data_clear_2.iloc[j, 3] = random.choice(date_list)

data_clear_2.replace('None', 0, inplace=True)
data_clear_2['评论数量'] = data_clear_2['评论数量'].astype('int')  # 转换为数值类型

# 筛选无法转换为数值的字符串进行删除
data_clear_2['数'] = data_clear_2['数'].apply(pd.to_numeric, errors='coerce').fillna(0.0).astype('int')
data_clear_2 = data_clear_2.drop(data_clear_2.loc[data_clear_2['数'] == 0].index).reset_index(drop=True)

str_list = []
for i in range(len(data_clear_2['价格'])):
    if pd.to_numeric(data_clear_2.iloc[i, 5], errors='ignore'):  # 判断转换是否可以转换为数值类型
        data_clear_2.iloc[i, 5] = pd.to_numeric(data_clear_2.iloc[i, 5], errors='ignore')  # 保留可转换的字符串
    else:
        str_list.append(i)  # 不可转换的字符串行索引列表

data_clear_3 = data_clear_2.drop(str_list).reset_index(drop=True)  # 删除后,重置索引
data_clear_3 = data_clear_3.iloc[1:]  # 特殊字符串
data_clear_3['价格'] = data_clear_3['价格'].apply(pd.to_numeric, errors='coerce').fillna(0.0).astype('float')
data_clear_3['出版时间'] = pd.to_datetime(data_clear_3['出版时间']).dt.floor('d')  # 转换时间格式
# print(data_clear_3.info())
# print('-' * 100)
# print(data_clear_3.describe())


# 描述性统计
# 书名
data_bookname = data_clear_3.groupby('书名').agg({'作者': 'count'}) \
    .sort_values(by='作者', ascending=False).head().reset_index()  # 按书名分类,统计总数
# g1 = sns.barplot(x='书名', y='作者', data=data_bookname)
# for i, row in data_bookname.iterrows():
#     g1.text(row.name, row.作者, row.作者, ha='center', c='black')
data_bookname_rank = data_clear_3[data_clear_3['书名'].isin(data_bookname['书名'].values)].sort_values(
    by='书名').reset_index(drop=True).iloc[:, :3]
# print(data_bookname_rank)


# 作者
data_drop_dup_bookname = data_clear_3.drop_duplicates(subset='书名', keep='first').reset_index(drop=True)  # 删除重复书名
# print(data_drop_dup_bookname)
data_author = data_drop_dup_bookname.groupby('作者').agg({'书名': 'count'}) \
    .sort_values('书名', ascending=False).head(5).reset_index()  # 按作者分类,统计总数
# g2 = sns.barplot(x='作者', y='书名', data=data_author)
# for i, row in data_author.iterrows():
#     g2.text(row.name, row.书名, row.书名, ha='center', c='black')

# print(data_drop_dup_bookname)
# print('-'*100)
# print(data_drop_dup_bookname[data_drop_dup_bookname['作者']== '亦舒'].sort_values(by='评分',ascending=False).head().reset_index(drop=True))
# print('-'*100)
# print(data_drop_dup_bookname[data_drop_dup_bookname['作者'] == '鲁迅'].sort_values(by='评分',ascending=False).head().reset_index(drop=True))


# 出版社
data_publisher = data_drop_dup_bookname.groupby('出版社').agg({'书名': 'count'}) \
    .sort_values('书名', ascending=False).head().reset_index()  # 按出版社分类,统计总数
# print(data_publisher)
# g3 = sns.barplot(x='出版社', y='书名', data=data_publisher)
# for i, row in data_publisher.iterrows():
#     g3.text(row.name, row.书名, row.书名, ha='center', c='black')
# print('-'*100)
# for i in data_publisher['出版社']:
#     print(data_drop_dup_bookname[data_drop_dup_bookname['出版社']== i].
#           sort_values(by='评分',ascending=False).head(1).reset_index(drop=True))
#     print('-' * 100)


# 出版时间
data_drop_dup_bookname['year'] = data_drop_dup_bookname['出版时间'].dt.year
data_drop_dup_bookname['month'] = data_drop_dup_bookname['出版时间'].dt.month
data_drop_dup_bookname['year_month'] = data_drop_dup_bookname['出版时间'].apply(lambda x: x.strftime('%Y-%m'))

data_year_cnt = data_drop_dup_bookname.groupby('year').agg({'书名': 'count'}).reset_index()  # 按年份统计
# print(data_year_cnt)
# g4_year = sns.pointplot(x='year', y='书名', data=data_year_cnt)
# for i, row in data_year_cnt.iterrows():
#     g4_year.text(row.name, row.书名+1, row.书名, ha='left', c='black')
# print('-'*100)
# plt.show()

data_month_cnt = data_drop_dup_bookname.groupby('month').agg({'书名': 'count'}).reset_index()  # 按年份统计
# print(data_month_cnt)
# g4_month = sns.pointplot(x='month', y='书名', data=data_month_cnt)
# for i, row in data_month_cnt.iterrows():
#     g4_month.text(row.name, row.书名+1, row.书名, ha='left', c='black')
# print('-'*100)
# plt.show()

data_year_month_cnt = data_drop_dup_bookname.groupby('year_month').agg({'书名': 'count'}).reset_index()  # 按年份统计
# print(data_year_month_cnt)
# g4_year_month = sns.pointplot(x='year_month', y='书名', data=data_year_month_cnt)
# for i, row in data_year_month_cnt.iterrows():
#     g4_year_month.text(row.name, row.书名+1, row.书名, ha='left', c='black')
# print('-'*100)
# print(data_drop_dup_bookname.groupby('month').agg({'评分': 'mean'}).reset_index())
# plt.show()


# 数
data_page = data_drop_dup_bookname.groupby('数').agg({'书名': 'count'}) \
    .sort_values(by='数', ascending=False).reset_index().rename(columns={'书名': 'nums'})
# print(data_page.describe())
# print(data_drop_dup_bookname[data_drop_dup_bookname['数'] == 240000])
# print('-'*100)

# 最高前10页数
data_page_head = data_page.head(10).reset_index(drop=True)
# print(data_page_head)
# for i in data_page_head['数']:
#     print(data_drop_dup_bookname[data_drop_dup_bookname['数'] == i][['书名','ISBM','数']])
# print('-'*100)

# 最低前10页数
data_page_tail = data_page.tail(10).reset_index(drop=True)
# print(data_page_tail)
# for i in data_page_tail['数']:
#     print(data_drop_dup_bookname[data_drop_dup_bookname['数'] == i][['书名','ISBM','数']])
# print('-'*100)


# 价格
# print(len(data_drop_dup_bookname))
data_price = data_drop_dup_bookname.drop(data_drop_dup_bookname[data_drop_dup_bookname['价格'] < 0.5].index)
# print(len(data_drop_dup_bookname))
data_price = data_price.groupby('价格').agg({'书名': 'count'}) \
    .sort_values(by='价格', ascending=False).reset_index().rename(columns={'书名': 'nums'})
# print(data_price.describe())
# print(data_price)
# print(data_drop_dup_bookname[data_drop_dup_bookname['价格'] == 999])

data_price_nums = data_drop_dup_bookname.groupby('价格').agg({'书名': 'count'}) \
    .sort_values(by='书名', ascending=False).head().reset_index().rename(columns={'书名': 'nums'})
# g6 = sns.barplot(x='价格',y='nums',data=data_price_nums,order=data_price_nums['价格'].values.astype('int'))
# for i,row in data_price_nums.iterrows():
#     g6.text(row.name,row.nums+1,row.nums,ha='center',c='black')

data_price_quantile = data_price['价格'].quantile([i / 10 for i in range(11)])
# print(data_price_quantile)


# ISBM
# print(len(data_drop_dup_bookname))
# print(data_drop_dup_bookname['ISBM'].nunique())
data_duplicated = data_drop_dup_bookname['ISBM'].duplicated(keep=False)
# print(data_drop_dup_bookname[data_duplicated == True])


# 评分
data_grade = data_drop_dup_bookname.drop(data_drop_dup_bookname[data_drop_dup_bookname['评分'] == 0.0].index)
# print(data_grade['评分'].describe())
# print(data_grade[data_grade['评分'] == 2.5])
# print(data_grade[data_grade['评分'] == 10.0])

data_grade_g = data_grade.groupby('评分').agg({'书名': 'count'}) \
    .sort_values(by='书名', ascending=False).head(10).reset_index().rename(columns={'书名': 'nums'})
# g8 = sns.barplot(x='评分',y='nums',data=data_grade_g,order=data_grade_g['评分'].values)
# for i,row in data_grade_g.iterrows():
#     g8.text(row.name,row.nums+1,row.nums,ha='center',c='black')
# plt.show()

data_grade['scale'] = pd.cut(data_grade['评分'],
                             bins=[i for i in range(2, 11)],
                             labels=range(2, 10))
data_grade_s = data_grade.groupby('scale').agg({'价格': 'count'}).reset_index().rename(columns={'价格': 'nums'})
# print(data_grade_s)

# 评论数量
data_comment_h = data_drop_dup_bookname.groupby('评论数量').agg({'书名': 'count'}) \
    .sort_values(by='评论数量', ascending=False).reset_index().rename(columns={'书名': 'nums'})
# print(data_comment_h)
# print('-'*100)
# for i in data_comment_h['评论数量'].head():
#     print(data_drop_dup_bookname[data_drop_dup_bookname['评论数量'] == i][['书名','作者','评分','价格','评论数量','ISBM']])
#     print('-' * 100)

data_comment = data_drop_dup_bookname.groupby('评论数量').agg({'书名': 'count'}) \
    .sort_values(by='书名', ascending=False).reset_index().rename(columns={'书名': 'nums'})
data_comment = data_comment.drop(data_comment[data_comment['评论数量'] == 0].index).reset_index(drop=True)
# print(data_comment.describe())
data_comment = data_comment.head(10)
# g9 = sns.barplot(x='评论数量',y='nums',data=data_comment,order=data_comment['评论数量'].values)
# for i,row in data_comment.iterrows():
#     g9.text(row.name,row.nums+1,row.nums,ha='center',c='black')
# plt.show()

# Multiple_figures(data_clear_3)


# 数据分析
# 有哪些评分和作品数量都很高的作者
author_cnt_nums = data_drop_dup_bookname.groupby('作者').agg({'书名': 'count', '评分': 'mean'}).sort_values(by='书名',
                                                                                                      ascending=False).reset_index().rename(
    columns={'书名': 'nums'})
# print(author_cnt_nums.head(20)['作者'])
# print('-'*100)
author_top20 = data_drop_dup_bookname[data_drop_dup_bookname['作者'].isin(author_cnt_nums.head(20)['作者'])].sort_values(
    by=['作者', '评分', '评论数量'], ascending=False)
author_top20_bookname = author_top20.groupby('作者').head(1).reset_index(drop=True)
# print(author_top20)
# print('-'*100)
# print(author_top20_bookname)
# print(author_top20_bookname[author_top20_bookname['评分'] > 8.6]['书名'].reset_index(drop=True))


# 书籍的评分和评论数量之间是否存在相关性
data_corr = data_drop_dup_bookname.drop(data_drop_dup_bookname.loc[(data_drop_dup_bookname == 0).any(1)].index)
data_corr = data_corr.drop(data_corr[data_corr['数'] < 25].index)
data_corr = data_corr.drop(data_corr[data_corr['价格'] < 0.5].index)
# print(data_corr.describe())
# print('-'*100)
# print(data_corr.corr().round(2))
# sns.scatterplot(x='评论数量',y='评分',data=data_corr)
# plt.show()


# 哪些书籍值得推荐
rec_1 = \
data_drop_dup_bookname.groupby('作者').agg({'书名': 'count'}).sort_values('书名', ascending=False).head(10000).reset_index()[
    '作者']
rec_2 = \
data_drop_dup_bookname.groupby('出版社').agg({'书名': 'count'}).sort_values('书名', ascending=False).head(1000).reset_index()[
    '出版社']
recomment_book = data_drop_dup_bookname.loc[(data_drop_dup_bookname['作者'].isin(rec_1)) &
                                            (data_drop_dup_bookname['出版社'].isin(rec_2)) &
                                            (data_drop_dup_bookname['价格'] > 22.6) &
                                            (data_drop_dup_bookname['评分'] > 7.1) &
                                            (data_drop_dup_bookname['评论数量'] > 10)]
print(recomment_book.sort_values(by='评分', ascending=False).reset_index(drop=True).head(100).iloc[:, :-3])

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

inganxu

感谢您的支持!!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值