一、项目简介
(一)项目情况
推特用户 @dog_rates 的档案也叫做 WeRateDogs。以诙谐幽默的方式对人们的宠物狗评级。这些评级通常以 10 作为分母。但是分子呢?分子一般大于 10。如11/10、12/10、13/10 等,WeRateDogs 拥有四百多万关注者,曾受到国际媒体的报道。WeRateDogs 推特档案包括基本的推特信息,如 5000 多条推特,但并不包括所有数据,转发用户和喜爱用户是两个遗漏的列。不过档案中有一列包括每个推特文本,可以用来提取评级、狗的名字和 "地位" (即 doggo、floofer、pupper 和 puppo)。
tweet_json文件为 txt 格式,每一行为一条独立的 twitter 信息,格式为 JSON ,其中有转发和喜欢。
通过一个 神经网络 运行 WeRateDogs 推特档案中的所有图片,这个神经网络可以对狗的品种分类。结果:对图片预测 (只含前三名) 的文件包括每个推特 ID、图片 URL 和最自信预测对应的图片编号 (由于推特最多包含 4 个图片,所以编号为 1 到 4)。
(二)数据情况
twitter-archive-enhanced.csv,推特档案
tweet_json.txt,含转发和喜欢
image-predictions.tsv,根据推特档案里的图片预测的狗
1、twitter-archive-enhanced.csv,推特档案
字段含义:
tweet_id:档案中的推特 ID
in_reply_to_status_id:回复ID
in_reply_to_user_id:被回复推文原始用户ID
timestamp:发文时间
source:消息来源(使用设备)
text:推文内容
retweeted_status_id:转发ID
retweeted_status_user_id:转发用户ID
retweeted_status_timestamp:转发时间
expanded_urls:推文链接
rating_numerator:评分分子
rating_denominator:评分分母
name:宠物名
doggo:狗的成长阶段,分类变量
floofer:狗的成长阶段,分类变量
pupper:狗的成长阶段,分类变量
puppo:狗的成长阶段,分类变量
2、tweet_json.txt,含转发和喜欢
取一行在解析后的情况如下:
字段含义:
retweet_count: 转发数
favorite_count: 喜爱数
3、image-predictions.tsv,根据推特档案里的图片预测的狗
字段含义:
tweet_id:档案中的推特 ID
jpg_url:预测的图像资源链接
img_num:最可信的预测结果对应的图像编号
p1:算法对推特中图片的一号预测
p1_conf:算法的一号预测的可信度
p1_dog:一号预测该图片是否属于“狗”(有可能是其他物种,比如熊、马等)
p2:算法对推特中图片预测的第二种可能性
p2_conf:算法的二号预测的可信度
p2_dog:二号预测该图片是否属于“狗”
p3:算法对推特中图片预测的第三种可能性
p3_conf:算法的三号预测的可信度
p3_dog:三号预测该图片是否属于“狗”
(三)项目目的
收集包含图片但不包含转发内容的原始评级,另外,每条推特的数据,至少要包含转发数(retweet_count)和喜欢数(favorite_count)。最终对收集的数据进行评估清洗,并对清洗过的数据进行储存、分析和可视化。
二、数据处理
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as snsplt.style.use('ggplot')
%matplotlib inline
一) 导入数据
# 1、Read the twitter-archive-enhanced dataset
df_twitter = pd.read_csv(r'C:\Users\...\Desktop\python\Python Project\0_重新深入探索\Data\twitter-archive-enhanced.csv')# 2、Read the image_predictions file
df_image = pd.read_csv(r'C:\Users\...\Desktop\python\Python Project\0_重新深入探索\Data\image-predictions.tsv', sep='\t')# 3、Read the tweet_json file
import json
df_json = pd.DataFrame(columns = ['tweet_id', 'retweet_count', 'favorite_count'])
with open(r'C:\Users\...\Desktop\python\Python Project\0_重新深入探索\Data\tweet-json.txt') as file:for line in file: # 一行一行的读取txt数据并提取需要的数据status = json.loads(line)tweet_id = status['id_str']retweet_count = status['retweet_count']favorite_count = status['favorite_count']df_json = df_json.append(pd.DataFrame([[tweet_id, retweet_count, favorite_count]], columns=['tweet_id', 'retweet_count', 'favorite_count']))
df_json = df_json.reset_index(drop=True)
二)数据清洗
# Copy three dataframes first
df_twitter_clean = df_twitter.copy()
df_image_clean = df_image.copy()
df_json_clean = df_json.copy()
(一) df_json_clean
df_json_clean.head(2)
df_json_clean.info()
df_json_clean.describe(include=["O"])
总结: 没有缺失值,tweet_id没有重复值,所以只需要改变数据的类型
df_json_clean['tweet_id'] = df_json_clean['tweet_id'].astype('str')
df_json_clean['retweet_count'] = df_json_clean.retweet_count.astype('int')
df_json_clean['favorite_count'] = df_json_clean.favorite_count.astype('int')
# recheck
df_json_clean.info()
(二) df_image_clean
pd.set_option('display.max_columns',100) #显示100列
pd.set_option('display.max_rows',None) # 显示所有行(参数设置为None代表显示所有行,也可以自行设置数字)
pd.set_option('max_colwidth',200) # 设置数据的显示宽度,默认为50 (大于字段宽度则自适应宽度)
pd.set_option('expand_frame_repr', True) #禁止自动换行(设置为Flase不自动换行,True反之)
df_image_clean.head(2)
df_image_clean.info()df_image_clean['tweet_id'].value_counts().nlargest(2)
df_image_clean['p1_dog'].value_counts()
总结:tweet_id没有缺失值;p1_dog表明p1(狗的种类)存在不是狗种类的数据,这里剔除不是狗狗的数据(p1_dog为False);然后需要剔除掉无用列;再转换数据的类型
# 删除不是狗狗的数据
df_image_clean = df_image_clean[df_image_clean['p1_dog'] == True]# 删除掉多余列
df_image_clean = df_image_clean.drop(columns=['p1_dog','p2','p2_dog','p2_conf','p3','p3_conf','p3_dog']) # 将预测出的种类名称全部变成小写
df_image_clean['p1'] = df_image_clean['p1'].map(lambda x: x.lower())# 修改数据类型
df_image_clean['tweet_id'] = df_image_clean['tweet_id'].astype('str')
df_image_clean['jpg_url'] = df_image_clean['jpg_url'].astype('str')
df_image_clean['img_num'] = df_image_clean['img_num'].astype('int')
df_image_clean['p1'] = df_image_clean['p1'].astype('str')
df_image_clean['p1_conf'] = df_image_clean['p1_conf'].astype('float')# recheck
df_image_clean.info()df_image_clean.head(2) # 处理后的最终结果
(三) df_twitter_clean
df_twitter_clean.sample(2)
1、总体情况
df_twitter_clean.info()plt.figure(figsize=(10,6))
sns.heatmap(df_twitter.isnull(), cbar=False) # 查看各字段缺失值情况,白色越多,缺失值(nan)越多,注意这里None不会按缺失值显示
plt.show()df_twitter.isnull().sum() # 看各字段缺失值情况df_twitter['tweet_id'].value_counts().nlargest(2)df_twitter[-df_twitter['retweeted_status_id'].isnull()].count()
总结:
1、有5个字段缺失非常多,因此删除掉;
2、其中由retweeted_status_id可以看到 属于转发别人的推文有181条,为避免重复我们只对原始推文进行分析,即剔除retweeted_status_id不为空的数据;
3、expanded_urls也存在59条缺失值,剔除expanded_urls有缺失的行(即没有照片的数据)
4、同样需要改变字段的数据类型
# 1.1、删除转发的(因为转发别人的可能重复,所以只保留原创)
df_twitter_clean = df_twitter_clean[df_twitter_clean['retweeted_status_id'].isnull() == True]df_twitter_clean['retweeted_status_id'].value_counts()# 1.2、剔除expanded_urls有缺失的行
df_twitter_clean = df_twitter_clean[df_twitter_clean['expanded_urls'].isnull() == False]df_twitter_clean['expanded_urls'].isna().sum()# 1.3、删除缺失值非常多5列
df_twitter_clean = df_twitter_clean.drop(columns=['in_reply_to_status_id','in_reply_to_user_id','retweeted_status_id','retweeted_status_user_id','retweeted_status_timestamp'])# 1.4、修改数据类型
df_twitter_clean['tweet_id'] = df_twitter_clean['tweet_id'].astype('str')
df_twitter_clean['timestamp'] = pd.to_datetime(df_twitter_clean['timestamp'])
df_twitter_clean.info()df_twitter_clean.head(2)
2、对source进行处理
df_twitter_clean['source'][0]
总结:我们只需要> 与 之间的数据,即这里的 “Twitter for iPhone”
# 2、用正则提取需要的数据
import re
temp = re.compile('>(.*)</a>') # 编译正则规则
df_twitter_clean['source'] = df_twitter_clean['source'].map(lambda x: temp.findall(x)[0])df_twitter_clean['source'].value_counts()
3、对expanded_urls进行处理
df_twitter_clean['expanded_urls'].value_counts().nlargest(5)df_twitter_clean[df_twitter_clean['expanded_urls'].duplicated()].count()# 3、查看expanded_urls重复的行
df_twitter_clean[df_twitter_clean['expanded_urls'] == 'https://vine.co/v/ea0OwvPTx9l']
总结:expanded_urls 有一个重复值,但是定位到内容后发现推文描述不一样,故不用进行去重处理
注:去重的方法:df_twitter_clean = df_twitter_clean.drop_duplicates(subset = 'expanded_urls', keep='first')
4、对rating_numerator、rating_denominator进行处理
df_twitter_clean[['rating_numerator','rating_denominator']].describe()# df_twitter_clean.rating_numerator.hist(figsize=(10,6),bins=10)
# df_twitter_clean.rating_denominator.hist(figsize=(10,6),bins=20)df_twitter_clean.rating_numerator.value_counts()
df_twitter_clean.rating_denominator.value_counts()
1) 对于rating_denominator
df_twitter_clean[df_twitter_clean.rating_denominator >10].count()df_twitter_clean[df_twitter_clean.rating_denominator >10]
df_twitter_clean[df_twitter_clean.rating_denominator <10]
可以看到,分母大于10的有16条数据,小于10的有2条数据,经查看发现其分子和分母属于同一数量级的,所以不用剔除;但是看到推文中可能有多个评分的情况
2) 对于rating_numerator
df_twitter_clean.rating_numerator.value_counts()
过了14就是 24的评分,将大于14的单独查看
df_twitter_clean[df_twitter_clean.rating_numerator > 14]
可以看到评分提取有误,有小数点的数据提取不全,需要重新
# 重新提取评分数据
import re
df_twitter_clean['score_new'] = df_twitter_clean['text'].map(lambda x: re.findall('\d+\.\d+/\d+|\d+/\d+',x)) # 提取有小数点和没有小数点的所有数据
df_twitter_clean['score_new'].value_counts()
这里也可以看到评分数据有多个的情况,下面查看一些评分有多个的数据情况
# 查看评分有多个的数据情况
df_twitter_clean['score_new_len'] = df_twitter_clean['score_new'].map(lambda x: len(x))
df_twitter_clean['score_new_len'].value_counts()df_twitter_clean[df_twitter_clean['score_new_len']>1]
当评分数据大于一个时,剔除该行,因为其可能发了多种狗狗的评论,其评分分别对应不同种的狗狗
df_twitter_clean = df_twitter_clean[df_twitter_clean['score_new_len'] == 1]
df_twitter_clean['score_new'].value_counts()
df_twitter_clean.head(2)df_twitter_clean['score_new_01'] = df_twitter_clean['score_new'].map(lambda x: x[0].split('/')[0]).astype(float)
df_twitter_clean['score_new_02'] = df_twitter_clean['score_new'].map(lambda x: x[0].split('/')[1]).astype(float)df_twitter_clean['score_new_01'].value_counts()
df_twitter_clean[(df_twitter_clean['score_new_01']>30) & (df_twitter_clean['score_new_02']<=10)]
可以看到分子/分母大于3的只有这两条数据,过于异常,剔除掉
df_twitter_clean = df_twitter_clean[~((df_twitter_clean['score_new_01']>30) & (df_twitter_clean['score_new_02']<=10))]df_twitter_clean['score_new_01'].value_counts()
df_twitter_clean['score_new_02'].value_counts()
df_twitter_clean[df_twitter_clean.rating_denominator == 10]['score_new_01'].value_counts()
4) 之前与新提取得分的差异
df_twitter_clean[df_twitter_clean['score_new_01'] != df_twitter_clean['rating_numerator']]
df_twitter_clean[df_twitter_clean['score_new_02'] != df_twitter_clean['rating_denominator']]
结果:只有4条数据与之前不一样,即只更新了小数点提取有误的数据,数据处理无误
#替换名称,删除临时字段
df_twitter_clean['rating_numerator'] = df_twitter_clean['score_new_01']
df_twitter_clean['rating_denominator'] = df_twitter_clean['score_new_02']df_twitter_clean = df_twitter_clean.drop(columns=['score_new_01','score_new_02','score_new','score_new_len'])df_twitter_clean.head(2)
5) 再次检查分子分母情况
x = df_twitter_clean[df_twitter_clean['rating_numerator']<= 20.0].rating_numerator
sns.distplot(x, kde=False)
plt.xlabel('Ratings')
plt.ylabel('Count')
plt.title('Distribution of Ratings')
plt.show()df_twitter_clean['rating_numerator'].value_counts()
sns.distplot(df_twitter_clean['rating_denominator'], kde=False, rug=True)
plt.show()df_twitter_clean['rating_denominator'].describe()
5、对name进行处理
df_twitter_clean['name'].value_counts()# 对字段:name进行分析
vals = df_twitter_clean[~df_twitter_clean['name'].str[0].str.isupper()]['name'].value_counts()
vals.keys()
总结:name字段存在不是名字的情况,对这样的数据进行剔除
# 5、把不正确的名字转化成 None
for val in vals.keys():df_twitter_clean['name'] = df_twitter_clean['name'].replace(val,'None')df_twitter_clean[~df_twitter_clean['name'].str[0].str.isupper()]['name'].value_counts() # 检查是否还有小写
6、把最后4列进行合并到一列
df_twitter_clean.info()df_twitter_clean['stage'] = df_twitter_clean['doggo']+'|'+df_twitter_clean['floofer']+'|'+df_twitter_clean['pupper']+'|'+df_twitter_clean['puppo']
df_twitter_clean['stage'].head()df_twitter_clean['stage']=df_twitter_clean.stage.str.lower().str.findall('(doggo|pupper|puppo|floof)')# 查看有多个stage的数据
df_twitter_clean['stage_len'] = df_twitter_clean['stage'].map(lambda x: len(x))
df_twitter_clean['stage_len'].value_counts()df_twitter_clean[df_twitter_clean['stage_len']>1]
可以看到,并非都是对一只狗狗的stage,为减少干扰,剔除有多种stage的12条数据
df_twitter_clean = df_twitter_clean[df_twitter_clean['stage_len'] <= 1]df_twitter_clean['stage_len'].value_counts()df_twitter_clean = df_twitter_clean.drop(columns=['stage_len']) df_twitter_clean['stage'].value_counts()# #将缺失值替换为np.nan
df_twitter_clean['stage']=df_twitter_clean['stage'].apply(lambda x: '.'.join(x))
df_twitter_clean['stage']=df_twitter_clean['stage'].replace('',np.nan)
df_twitter_clean['stage'].value_counts()df_twitter_clean['stage'].isnull().sum()df_twitter_clean.head(2)# 删除掉多余列
df_twitter_clean = df_twitter_clean.drop(columns=['doggo','floofer','pupper','puppo'])
df_twitter_clean.head(2)
(四) Combine data
# 1 查看3张表的数据情况
print(len(df_twitter_clean))
print(len(df_image_clean))
print(len(df_json_clean))# recheck
df_twitter_clean.info()
df_image_clean.info()
df_json_clean.info()
df_twitter_clean.head(2)
df_image_clean.head(2)
df_json_clean.head(2)
# 2 合并数据-inner
# merge df_twitter_clean with df_json_clean
main_df = df_twitter_clean.merge(df_json_clean, on='tweet_id',how='inner')# Merge the archive_image and df_json_clean on tweet_id
master = main_df.merge(df_image_clean, on='tweet_id', how='inner')master.head(2)
# master.info()
(五) Storing
import os
os.getcwd() # 获取当前路径# 保存处理后的数据
master.to_csv('twitter_archive_master.csv', index=False)
三、数据分析
(一)Q1: 哪个种类的狗狗最受欢迎?
types = master.p1.value_counts()
types
fig, ax = plt.subplots(figsize=(10,6))
plt.bar(types[0:10].index,types[0:10]) # 只对前10种进行展示
plt.xticks(rotation=45) # 设置x轴刻度角度,或者: plt.xticks(rotation='vertical')
plt.xlabel('Dogs')
plt.ylabel('Number')
plt.title('Top 10 most popular dogs')
plt.show()
结论:可以看到,最受欢迎的狗狗是golden_retriever,其次是labrador_retriever和pembroke。
(二)Q2: 哪个名字给狗狗取的最多?
# from collections import Counter # Counter是一个dict子类,主要是用来对你访问的对象的频率进行计数。
# x = master['name']
# count = Counter(x)
# count.most_common(11)# 以上是通过导入Counter计数模块来查找出现频率最高的11项,也可以通过以下代码实现
master['name'].value_counts().nlargest(11)
names = master['name'].value_counts().nlargest(10)
fig, ax = plt.subplots(figsize=(10,6))
plt.bar(names.index,names) # 只对前10种进行展示
plt.xticks(rotation=45) # 设置x轴刻度角度,或者: plt.xticks(rotation='vertical')
plt.xlabel('Name')
plt.ylabel('Number')
plt.title('Top 10 most popular Names')
plt.show()
结论:可以看到,大多数推特中没有提到狗狗的名字,但在有名字的数据中,取名Cooper是最多的。
(三)Q3: 得分、喜爱数、转发数直接有什么关系?
master['score'] = master['rating_numerator']/master['rating_denominator']
master['score'].value_counts()
pd.plotting.scatter_matrix(master[['favorite_count','retweet_count','score']],figsize=(6,6),diagonal='hist')
plt.show()
plt.subplots(figsize=(10,6))
sns.heatmap(master[['favorite_count','retweet_count','score']].corr(),annot=True, vmax=1,vmin = 0, xticklabels= True, yticklabels= True, square=True, cmap="Reds")
plt.xticks(rotation=45)
plt.yticks(rotation=45)
plt.show()
结论:可以看到,rating_numerator 与 favorite_count、retweet_count 都没有什么明显的关系; 但是favorite_count与retweet_count有明显的线性关系,其相关系数也比较高,为0.93。
(四)Q4: 喜爱数、转发数随时间的变化关系?
time = pd.to_datetime(master[master.favorite_count < 80000].timestamp).dt.date
fig, ax = plt.subplots(figsize=(10,6))
plt.plot_date(time,master[master.favorite_count < 80000].retweet_count,color='y' , label='retweet_count')
plt.plot_date(time,master[master.favorite_count < 80000].favorite_count, color='r', label='favorite_count')
plt.xlabel('Date')
plt.ylabel('Number')
plt.title('The number of retweet and favorite change over time')
plt.legend()
ax.xaxis.set_tick_params(rotation=45, labelsize=10) # 设置x轴ticks的角度,大小
plt.show()
master['rating'] = master['retweet_count']/master['favorite_count']
fig, ax = plt.subplots(figsize=(10,6))
# plt.plot_date(time,master[master.favorite_count < 80000].retweet_count,color='y' , label='retweet_count')
# plt.plot_date(time,master[master.favorite_count < 80000].favorite_count, color='r', label='favorite_count')
plt.plot_date(time,master[master.favorite_count < 80000].rating, label='rating')
plt.xlabel('Date')
plt.ylabel('rating')
plt.title('The Rating(retweet/favorite) change over time')
plt.legend()
ax.xaxis.set_tick_params(rotation=45, labelsize=10) # 设置x轴ticks的角度,大小
plt.show()
结论:可以看到, favorite_count一直比retweet_count要高,但是2016年9月以前,两者总体比较接近,之后两者总体差距变大。即 retweet_count与favorite_count的比率 Rating 有所下降。
(五)Q5:对不同stage的狗,推文中的描述有什么不同?(词云图)
from wordcloud import WordCloud
import remaster['text'][0] # 查看文本情况
# all
mytext = ''
for i in master['text']:mytext = mytext + re.findall('(.*)https:',i)[0]mytext = mytext + "|"# %pylab inline
# import matplotlib.pyplot as plt
wordcloud = WordCloud().generate(mytext)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()
(六)Q6: 不同stage狗狗的受欢迎情况
count_by_stage = master.stage.value_counts()
count_by_stagemean_favorite_count = master.groupby('stage').favorite_count.mean()
mean_favorite_countmean_retweet_count = master.groupby('stage').retweet_count.mean()
mean_retweet_count
fig, ax = plt.subplots(figsize=(10,6))
ax.bar(mean_favorite_count.index, mean_favorite_count, width=0.3, align='edge', alpha=0.8)
ax.bar(mean_retweet_count.index, mean_retweet_count, width=0.3, align='center', alpha=0.8)
ax.legend(labels=['mean_favorite_count','mean_retweet_count'],loc='best')
# ax.grid(b=False)plt.xlabel('stage')
plt.ylabel('mean')
plt.title('The popularity of dogs at different stages')ax2 = ax.twinx()
ax2.plot(count_by_stage.index, count_by_stage, 'y-')
ax2.legend(labels=['count_by_stage'],loc='best')
ax2.grid(b=False)
plt.ylabel('count')
结论:对有stage状态的数据,可以看到评论数据条数中,
1、推文数:pupper、doggo、puppo、floof 的数据依次递减;
2、喜爱数:floof、pupper、doggo、puppo 的数据依次递减;
3、转发数:floof、pupper、doggo、puppo 的数据依次递减;
(七)Q7: source 情况分析
master.source.value_counts()
结论:绝大部分的推文都是通过 iPhone手机 发出的。
附:扩展-【聚合 pivot_table】
透视解析:
链接1:Python中pandas透视表pivot_table功能详解(非常简单易懂) - The-Chosen-One - 博客园
链接2:Python——数据透视(介绍pivot_table函数的使用方法) - 小猪课堂 - 博客园
# 透视表
# 会自动计算数据的平均值,但是我们也可以对该列元素进行计数或求和。要添加这些功能,使用aggfunc和np.sum就很容易实现
pd.pivot_table(master,index=['stage','source'],values=['score','favorite_count','retweet_count','rating'], aggfunc=np.mean)
# pd.pivot_table(master,index=['source','stage'],values=['score','favorite_count','retweet_count','rating'], aggfunc=len)
pd.pivot_table(master,index=['source','stage'],values=['score','favorite_count','retweet_count','rating'], aggfunc=[np.mean,len])
pd.pivot_table(master,index=['source','p1'],values=['tweet_id'], aggfunc=len)
# 对每个字段指定聚合方式
pd.pivot_table(master,index=['source'],values=['tweet_id','score'], aggfunc={'tweet_id': len, 'score': np.mean})