01 前言
我们每天都在使用从手机应用商店里下载的App,有没有想过什么样的App是最受欢迎的呢?一个最直接的方法,就是前往应用商店,对App的属性进行分析,得出受欢迎的应用的特点,可以辅助新的App开发设计或是掌握当下人们使用App的流行趋势。
Google Play Store 是在国外手机用户下载安卓应用程序的商店,今天的案例是对商店中app的统计数据进行分析,重点掌握业务分析中数据清洗的方法。
公号回复:DT17 获取数据 googleplaystore.csv
02 数据观察
如图所示,App的属性信息在网页可以轻松获取,此份数据也是从网络上爬取下来的。
观察数据,如下图所示,第一行是列名,含有App程序名称,Category类别,Rating评分,Reviews评论数,Size程序大小,Installs安装数量等等,总共有1w条数据。了解数据过后,接下来用Python读取csv文件。
import numpy as np
import pandas as pd
df = pd.read_csv('./googleplaystore.csv',usecols=(0,1,2,3,4,5,6)) # usecols取7列数据
df.head() # 观察前几行数据 了解字段含义
df.describe() # 只有rating列有描述统计 说明其他列的数据类型是字符串
Rating | |
---|---|
count | 9367.000000 |
mean | 4.193338 |
std | 0.537431 |
min | 1.000000 |
25% | 4.000000 |
50% | 4.300000 |
75% | 4.500000 |
max | 19.000000 |
df.count() # 统计每一列的行数 发现Rating行数少一千多行,Type少一行
# 有重复、缺失和异常的数据,接下来需要对数据进行清洗
App 10841
Category 10841
Rating 9367
Reviews 10841
Size 10841
Installs 10841
Type 10840
dtype: int64
03 数据清洗
# 数据清洗,一列一列地进行分析,判断是否有重复值和缺失值以及异常值
# App列清洗,由于Rating 评论数数小于App数,猜测App有重复值
pd.unique(df['App']).size
9660
# 发现App的unique行数是9660,说明有重复值,但是在第一列先不进行去重,因为可能有App名重名的情况,需要结合其他列判断
# Category 列处理
df['Category'].value_counts(dropna=False) # 空值也统计
FAMILY 1972
GAME 1144
TOOLS 843
MEDICAL 463
BUSINESS 460
PRODUCTIVITY 424
PERSONALIZATION 392
COMMUNICATION 387
SPORTS 384
LIFESTYLE 382
FINANCE 366
HEALTH_AND_FITNESS 341
PHOTOGRAPHY 335
SOCIAL 295
NEWS_AND_MAGAZINES 283
SHOPPING 260
TRAVEL_AND_LOCAL 258
DATING 234
BOOKS_AND_REFERENCE 231
VIDEO_PLAYERS 175
EDUCATION 156
ENTERTAINMENT 149
MAPS_AND_NAVIGATION 137
FOOD_AND_DRINK 127
HOUSE_AND_HOME 88
LIBRARIES_AND_DEMO 85
AUTO_AND_VEHICLES 85
WEATHER 82
ART_AND_DESIGN 65
EVENTS 64
COMICS 60
PARENTING 60
BEAUTY 53
1.9 1
Name: Category, dtype: int64
# 发现类别名,没有空值;
# 末尾一行的类别名为1.9,可能是异常值,挑选出来进行观察
df[df['Category'] == '1.9']
# Category为1.9 Rating为19(rating一般为1-5分)Size为1000+(应该写为xxM) Type应该是Free
# 判断这一行,缺失了category,应该把1.9往后的数据往右移动一列,实际操作中可以在网页页面上查找到类别补充上去
# 由于补充信息并移动列操作比较繁琐,数据有一万多行,直接删除这一行影响不大
df.drop(index=10472, inplace=True)
# Rating列处理
df['Rating'].value_counts(dropna=False)
NaN 1474
4.4 1109
4.3 1076
4.5 1038
4.2 952
4.6 823
4.1 708
4.0 568
4.7 499
3.9 386
3.8 303
5.0 274
3.7 239
4.8 234
3.6 174
3.5 163
3.4 128
3.3 102
4.9 87
3.0 83
3.1 69
3.2 64
2.9 45
2.8 42
2.7 25
2.6 25
2.5 21
2.3 20
2.4 19
1.0 16
2.2 14
1.9 13
2.0 12
1.7 8
1.8 8
2.1 8
1.6 4
1.5 3
1.4 3
1.2 1
Name: Rating, dtype: int64
# Rating的分布在[0-5]之间,数据没有问题,但是NaN空值有1474个,影响较大,需要进行处理,在这里给NaN赋上平均值;
df['Rating'].fillna(value=df['Rating'].mean(), inplace=True)
# fillna()函数,对NaN进行赋值
df['Rating'].value_counts(dropna=False)
4.191757 1474
4.400000 1109
4.300000 1076
4.500000 1038
4.200000 952
4.600000 823
4.100000 708
4.000000 568
4.700000 499
3.900000 386
3.800000 303
5.000000 274
3.700000 239
4.800000 234
3.600000 174
3.500000 163
3.400000 128
3.300000 102
4.900000 87
3.000000 83
3.100000 69
3.200000 64
2.900000 45
2.800000 42
2.700000 25
2.600000 25
2.500000 21
2.300000 20
2.400000 19
1.000000 16
2.200000 14
1.900000 13
2.000000 12
1.800000 8
1.700000 8
2.100000 8
1.600000 4
1.500000 3
1.400000 3
1.200000 1
Name: Rating, dtype: int64
# Reviews列处理
df['Reviews'].value_counts(dropna=False)
0 596
1 272
2 214
3 175
4 137
5 108
6 97
7 90
8 74
9 65
10 64
12 60
11 52
13 49
17 48
19 41
14 41
16 35
21 35
20 35
15 31
30 30
24 30
25 30
38 29
18 27
22 26
23 25
27 25
33 24
...
127229 1
2159 1
157264 1
6826 1
21262 1
37607 1
71269 1
67071 1
24215 1
63624 1
10753 1
159455 1
72596 1
8191 1
258556 1
10672 1
454412 1
56065 1
42329 1
84114 1
71432 1
815893 1
654419 1
9562 1
580 1
2976 1
18478 1
73821 1
1740 1
354 1
Name: Reviews, Length: 6001, dtype: int64
# Reviews 每个app评论数的分布非常广,评论为0的情况最多,有596个
# 在开头使用describe函数,并没有出现reviews列的统计信息,这一列中的数据可能含有字符串,数据格式可能不对
df['Reviews'].str.isnumeric().sum()
10840
# 10840个数值型数据,没有字符串数据
# 之前在category列中删除一行后,说明reviews列剩下的都是数值型的数据
# 也可以进行验证是否存在非数值型数据
df[-df['Reviews'].str.isnumeric()]
# 发现都是数值型数据,再次使用describe函数,是否出现reviews的列描述
df.describe() # 没有出现reviews列的统计信息,可能是数字格式不对
# 统一将数据格式转换
df['Reviews'] = df['Reviews'].astype('i8') # int8
df.describe()
# 最大的评论数有7.815831e+07 7.8乘以10的7次方,也就是百万次,最小是0,没有出现负值,数据合理
# Size的清洗处理
df['Size'].value_counts(dropna=False)
Varies with device 1695
11M 198
12M 196
14M 194
13M 191
15M 184
17M 160
19M 154
26M 149
16M 149
25M 143
20M 139
21M 138
24M 136
10M 136
18M 133
23M 117
22M 114
29M 103
27M 97
28M 95
30M 84
33M 79
3.3M 77
37M 76
35M 72
31M 70
2.9M 69
2.3M 68
2.5M 68
...
245k 1
860k 1
67k 1
942k 1
629k 1
940k 1
208k 1
787k 1
785k 1
14k 1
921k 1
116k 1
234k 1
378k 1
865k 1
226k 1
122k 1
222k 1
400k 1
191k 1
549k 1
642k 1
209k 1
778k 1
540k 1
240k 1
663k 1
220k 1
11k 1
485k 1
Name: Size, Length: 461, dtype: int64
# Varies with device 1695 ,size大小不确定,因此不方便分析,同使用均值代替
# 计算时,Size带有M和K的单位,不方便计算,因此需要去掉
df['Size'] = df['Size'].str.replace('M','e+6')
df['Size'] = df['Size'].str.replace('k','e+3')
# 尝试转换数据类型,此时转换报错,还有字符串
# df['Size'].astype('f8')
# 定义一个字符串判断判断数据是否可以转换
def is_convertable(v):
try:
float(v)
return True
except ValueError:
return False
df['Size'].apply(is_convertable)
# 查看不能转换的字符串分布 即含有false的项
temp = df['Size'].apply(is_convertable)
df['Size'][-temp].value_counts()
# 转换剩下的字符串
df['Size'] = df['Size'].str.replace('Varies with device', '0')
# 再次确认是否有未转换的字符串
temp = df['Size'].apply(is_convertable)
df['Size'][-temp].value_counts()
# 转换类型
# e+5 类似科学计数法格式的数据使用astype直接转为int有问题,转成int,可以先转成f8,再转i8
df['Size'] = df['Size'].astype('f8').astype('i8')
# 将Size为0的填充为平均数
df['Size'].replace(0, df['Size'].mean(), inplace=True)
df.describe()
# Installs数据清洗
# 查看分布,数值中带有逗号和加号
df['Installs'].value_counts()
1,000,000+ 1579
10,000,000+ 1252
100,000+ 1169
10,000+ 1054
1,000+ 907
5,000,000+ 752
100+ 719
500,000+ 539
50,000+ 479
5,000+ 477
100,000,000+ 409
10+ 386
500+ 330
50,000,000+ 289
50+ 205
5+ 82
500,000,000+ 72
1+ 67
1,000,000,000+ 58
0+ 14
0 1
Name: Installs, dtype: int64
# 替换 加号和逗号,以方便数据分析
df['Installs'] = df['Installs'].str.replace('+', '')
df['Installs'] = df['Installs'].str.replace(',', '')
# 转换类型
df['Installs'] = df['Installs'].astype('i8')
df.describe()
# Type处理
df['Type'].value_counts(dropna=False)
Free 10039
Paid 800
NaN 1
Name: Type, dtype: int64
# 有一行是NaN,最简单的做法 需要找出此行所在index并删除
df[df['Type'].isnull()]
# 删除这条数据
df.drop(index=9148, inplace=True)
# 最后其他列都清洗完毕后,对App去重,保证了数据行数的一致
df.drop_duplicates('App',inplace = True)
df.count()
App 9658
Category 9658
Rating 9658
Reviews 9658
Size 9658
Installs 9658
Type 9658
dtype: int64
04 数维度分析和相关性分析
# 以上数据清洗完成,接下来对每列进行分析
# 整体情况
df.describe()
# 选择维度进行分析
# 以Category类别为维度,分析哪些类别的App的最受欢迎
# 类别的个数
df.Category.unique().size
33 # 个类别
# 每个类别的App数量,排序,可以得出哪些分类的app最受开发者欢迎
df.groupby('Category').count().sort_values('App', ascending=False)
# 类别的安装量排序:娱乐社交类最被用户所需要
df.groupby('Category').mean().sort_values('Installs', ascending=False)
# 类别的评论数据:社交游戏视频评论多
df.groupby('Category').mean().sort_values('Reviews', ascending=False)
# 类别的评分数据:教育类评分最高
df.groupby('Category').mean().sort_values('Rating', ascending=False)
# 以Type (免费还是收费)来分析
df.groupby('Type').count()
# 只有两个类型,且数据量差别很大,没必要继续对比
df.groupby('Type').sum().sort_values('Installs', ascending=False)
# Category和Type一起分析
df.groupby(['Type', 'Category']).mean().sort_values('Reviews', ascending=False)
# 评论安装比
# 收费的app评论比率更高
g = df.groupby(['Type', 'Category']).mean()
(g['Reviews'] / g['Installs']).sort_values(ascending=False)
# 相关性:
#评论数和安装数强相关,其他的连0.1都不到,可以认为是不相关的(0.5以上可以认为是相关的,0.3以上可以认为是弱相关)
df.corr()
05 写在后面
我们对数据清洗的策略是按列进行分析是否有重复值,异常值和缺失值。如果某一列的数据去重后,少了一些行数,这可能并不是真的重复,会有出现重名的情况,因而不要盲目的将重复的数据删除,需要结合其他列进行判断。此外,从网页爬取的数据中,每列中的数据可能出现多种格式,为方便数值型数据的计算,还需要进行格式转换,并使用describe()验证。