数据来源:https://www.kaggle.com/datasnaek/youtube-new
Kaggle提供的数据集包括4个国家的热门YouTube视频的每日记录。每个国家的数据文件为一个CSV文件及一个JSON文件。
① 绘制每个国家指定列的的top10,如category,channel_title等;
② 统计视频发布后上榜的天数;
③ 查看views, likes, dislikes, comment_count的关系;
④ 按月份统计不同国家发布的视频数量,并用柱状图进行可视化。
以及附加要求中还有关联读写数据库、进行数据清洗等要求。
代码直接cv过去是无法运行的,有一些关于环境的bug。理解为重,酌情cv。
代码参考:数据分析~案例:YouTube视频趋势分析
在main_mac.py主程序文件中,虽然使用了MySQL数据库,实现了数据库的读写函数,但是实际上并没有从数据库读取或者写入。各位将读取文件的代码片转换成读取MySQL数据库的函数即可。
若使用MySQL Workbench等数据库GUI工具的话,可以直接通过图形界面将.csv文件和.json文件手动导入数据库中,无需代码导入,程序中只需完成读操作即可。
主程序在计算皮尔逊相关系数时,计算量较大,所花费的计算时间会较长,所以运行时在显示相关系数的图像之前需要等待一段时间。
#-*- coding: utf-8 -*-
'''
由于macOS自带且默认使用Python 2.7,这里声明Python版本以避免某些bug;
或者在终端运行时使用python3。
'''
#! python3
import os
import platform
import pandas as pd
import numpy as np
import json
import matplotlib.pyplot as plt
import seaborn as sns
from pyecharts import Bar, Line, Overlap
from matplotlib.font_manager import FontProperties
# import MySQLdb
import pymysql
import csv
import codecs
# 由于我使用纯文本编辑器编写,这里使用pysnooper进行调试
import pysnooper
# 导入config.py文件
import config
# 解决matplotlib显示中文问题
# 仅适用于Mac
def get_chinese_font():
global sysstr
# 若为macOS
if sysstr == 'Darwin':
# 使用 苹方-简 字体
return FontProperties(fname='/System/Library/Fonts/PingFang.ttc')
# 若为Linux
elif sysstr == 'Linux':
# 在Ubuntu测试平台上没有出现中文字体显示错误的问题
pass
# 这里调用pysnooper监测MySQL是否连接成功
@pysnooper.snoop()
def get_conn():
conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='root', db='test_csv', charset='utf8')
return conn
def insert_csv(cur, sql, args):
cur.execute(sql, args)
def query_all(cur, sql, args):
cur.execute(sql, args)
return cur.fetchall()
# 将本地的.csv文件写入到MySQL数据库中
def read_csv_to_mysql(filename):
with codecs.open(filename=filename, mode='r', encoding='utf-8') as f:
reader = csv.reader(f)
head = next(reader)
conn = get_conn()
cur = conn.cursor()
sql = 'insert into tb_csv values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
for item in reader:
if item[1] is None or item[1] == '': # item[1]作为唯一键,不能为null
continue
args = tuple(item)
# print(args)
insert(cur, sql=sql, args=args)
conn.commit()
cur.close()
conn.close()
# 将MySQL数据库中的表格写入到本地文件中
'''
适用于macOS、Linux的相对路径:
../data/filename
'''
def read_mysql_to_csv(filename):
with codecs.open(filename=filename, mode='w', encoding='utf-8') as f:
write = csv.writer(f, dialect='excel')
conn = get_conn()
cur = conn.cursor()
sql = 'select * from tb_csv'
results = query_all(cur=cur, sql=sql, args=None)
for result in results:
# print(result)
write.writerow(result)
# 从json文件中获取数据种类
def get_category_from_json(json_file):
category_dict = {
}
with open(json_file,'r') as f:
data=json.load(f)
for category in data['items']:
category_dict[int(category['id'])]=category['snippet']['title']
return category_dict
# 合并各个国家的数据
def combine_video_data():
video_df_list=[]
for country in config.countries:
print('数据处理:', country)
csv_filename=country+'videos.csv'
json_filename=country+'_category_id.json'
video_df=pd.read_csv(os.path.join(config.dataset_path,csv_filename), index_col='video_id',usecols=config.usecols)
# 处理时间列
video_df['trending_date']=pd.to_datetime(video_df['trending_date'],format='%y.%d.%m')
video_df['publish_time']=pd.to_datetime(video_df['publish_time'],format='%Y-%m-%dT%H:%M:%S.%fZ')
# 获取发布的日期
video_df['publish_time']=video_df['publish_time'].dt.date
video_df['publish_date'] = pd.to_datetime(video_df['publish_time'])
# 先按想要的格式转为字符串,再转为日期格式
video_df['publish_time'] = video_df.publish_time.apply(lambda x: x.strftime('%Y-%m'))
# 通过json文件获取category_id对应的名称
category_dict=get_category_from_json(os.path.join(config.dataset_path,json_filename))
# 通过map操作添加category名称列
video_df['category'] = video_df['category_id'].map(category_dict)
# 添加country列
video_df['country'] = country
video_df_list.append(video_df)
'''
# 预览数据
print('数据预览:')
print(video_df.head())
print(video_df['publish_time'])
'''
#各国家数据合并
all_video_df=pd.concat(video_df_list)
# 保存结果到一个数据集中
all_video_df.to_csv(os.path.join(config.output_path,'all_video.csv'))
return all_video_df
def plot_top10_by_country(video_df,col_name,title,save_filename):
"""
绘制每个国家指定列的的top10
参数:
- video_df: 处理后的数据
- col_name: 列名
- save_filename: 保存文件名
"""
fig,axes=plt.subplots(len(config.countries),figsize=(10,8))
if sysstr == 'Darwin':
# 为整幅图添加标题
fig.suptitle(title,fontproperties=get_chinese_font())
for i,country in enumerate(config.countries):
country_video_df=video_df[video_df['country']==country]
# 获取指定列的top10
top10_df=country_video_df[col_name].value_counts().sort_values(ascending=False)[:10]
# 处理x轴的刻度标签
x_labels=[label[:7]+'...' if len(label)>10 else label for label in top10_df.index]
# 制图
sns.barplot(x=x_labels,y=top10_df.values,ax=axes[i])
axes[i].set_xticklabels(x_labels, rotation=45)
axes[i].set_title(country)
#布局紧凑,不会超出画布
plt.tight_layout()
# 为子图顶部增加间隔,防止子图的标题和整幅图的标题重叠
plt.subplots_adjust(top=0.9)
#保存图片
plt.savefig(os.path.join(config.output_path, save_filename))
plt.show()
def plot_days_to_trend(video_df, save_filename):
"""
使用pyecharts统计视频发布后上榜的天数
参数:
- video_df: 处理后的数据
- save_filename: 保存文件名
"""
# pandas的版本>=0.20
video_df['diff'] = (video_df['trending_date'] - video_df['publish_date']).dt.days
days_df = video_df['diff'].value_counts()
# 观察视频发布2个月内的情况
days_df = days_df[(days_df.index >= 0) & (days_df.index <= 60)]
days_df = days_df.sort_index()
bar=Bar('视频发布2个月内的情况')
bar.add('柱状图', days_df.index.tolist(), days_df.values.tolist(),
is_datazoom_show=True, # 启用数据缩放功能
datazoom_range=[0, 50] # 百分比范围
)
line=Line()
line.add('折线图', days_df.index.tolist(), days_df.values.tolist())
overlap=Overlap()
overlap.add(bar)
overlap.add(line)
overlap.render(os.path.join(config.output_path,save_filename))
def plot_relationship_of_cols(all_video_df, cols):
"""
查看列之间的关系
"""
sel_video_df = all_video_df[cols + ['country']]
#变量间关系 pairplot
# #hue : 使用指定变量为分类变量画图。参数类型:string (变量名)
g=sns.pairplot(data=sel_video_df,hue='country')
g.savefig(os.path.join(config.output_path,'pair_plot.png'))
plt.show()
# heat map
# 计算每两个列之间的皮尔逊相关系数
'''
皮尔逊相关系数的变化范围为-1到1。
系数的值为1意味着 X 和 Y 可以很好的由直线方程来描述,
所有的数据点都 很好的落在一条 直线上,且 Y 随着 X 的增加而增加。
系数的值为−1意味着所有的数据点都落在直线上,且 Y 随着 X 的增加而减少。
系数的值为0意味着两个变量之间没有线性关系。
'''
corr_df=sel_video_df.corr()
sns.heatmap(corr_df,annot=True)
plt.savefig(os.path.join(config.output_path,'heat_map.png'))
plt.show()
# 按月统计各国视频上传量,并用柱状图进行可视化
@pysnooper.snoop()
def plot_monpub_by_country(video_df, title):
fig,axes=plt.subplots(len(config.countries),figsize=(10,8))
if sysstr == 'Darwin':
# 为整幅图添加标题
fig.suptitle(title,fontproperties=get_chinese_font())
for i,country in enumerate(config.countries):
country_video_df=video_df[video_df['country']==country]
gp = country_video_df.publish_time.value_counts()
'''
由于在数据集中,绝大多数的视频发布日期是2017-11月至2018-2月,极少数视频是在其它更早时间发布的,那些其余发布时间的视频数据就是我们此次需要清洗的数据。下面对的for循环作用是将总发布数量小于100的月份数据删除,完成清洗。
'''
for aline in gp.index:
# print(gp[aline])
if gp[aline] < 100:
gp.drop([aline], axis = 0, inplace = True)
print(gp)
# country_video_df.publish_time.value_counts().plot.bar()
# gp.plot.bar(width = 2)
# 处理x轴的刻度标签
x_labels=['...' if len(str(label))>10 else label for label in gp.index]
# 制图
sns.barplot(x=x_labels,y=gp.values,ax=axes[i])
axes[i].set_xticklabels(x_labels, rotation=45)
axes[i].set_title(country)
#布局紧凑,不会超出画布
plt.tight_layout()
# 为子图顶部增加间隔,防止子图的标题和整幅图的标题重叠
plt.subplots_adjust(top=0.9)
#保存图片
plt.savefig(os.path.join(config.output_path, 'month2.png'))
plt.show()
def main():
"""
主函数
"""
# 处理表格数据
all_video_df = combine_video_data()
# 绘制每个国家指定列的的top10
plot_top10_by_country(all_video_df, 'category', '各国的类别Top10', 'top10_category.png')
plot_top10_by_country(all_video_df, 'channel_title', '各国的频道Top10', 'top10_channel.png')
# 统计视频发布后上榜的天数
plot_days_to_trend(all_video_df, 'publish_vs_trend.html')
# 查看views,likes,dislikes,comment_count的关系
cols = ['views', 'likes', 'dislikes', 'comment_count']
plot_relationship_of_cols(all_video_df, cols)
# 按月统计不同国家发布视频的数量,并用柱状图进行可视化
plot_monpub_by_country(all_video_df, '月统计')
if __name__ == '__main__':
main()
config.py为配置文件。
import os
# 指定数据集路径
dataset_path='./data'
# 结果保存路径
output_path='./output'
if not os.path.exists(output_path):
os.mkdir(output_path)
countries=['CA','DE','GB','US']
# 使用的列
usecols=['video_id', 'trending_date', 'channel_title', 'category_id', 'publish_time', 'views', 'likes',
'dislikes', 'comment_count', 'comments_disabled', 'ratings_disabled', 'video_error_or_removed']
'''
video_id: 视频id,字符串
trending_date: 视频上榜的日期,字符串
title: 视频标题,字符串
channel_title: 所属频道标题,字符串
category_id: 所属类别编号,整型
publish_time: 视频发布时间,时间类型
tags: 视频标签,字符串
views: 观看次数,整型
likes: 点赞次数,整型
dislikes: 被踩次数,整型
comment_count: 评论次数,整型
comments_disabled: 评论是否关闭,布尔值
ratings_disabled: 打分是否关闭,布尔值
video_error_or_removed: 视频出错或者被删,布尔值
description: 视频详情,字符串
'''