原文:Matplotlib tutorial: Plotting tweets mentioning Trump, Clinton & Sanders
Python有多种可视化库,包括seaborn, networkx, 和vispy。大多数的Python可视化库全部或部分基于matplotlib,这往往是绘制简单的图的第一种手段,也是绘制那些难以在其他库绘制的图的最后一种手段。
在这个matplotlib教程中,我们将介绍该库的基本知识,并看看如何进行一些中间可视化。
我们将使用包含将近240,000条关于Hillary Clinton, Donald Trump, 和Bernie Sanders,目前所有美国总统候选人的推特的数据集。
该数据是从Twitter Streaming API拉过来的,而所有240,000条推特的csv文件可以在这里下载。如果你想自己爬取更多数据,那么你可以看看这里的爬虫代码。
在我们开始绘制之前,让我们加载数据并进行一些探索。我们可以使用Pandas,这个数据分析Python库,来帮助我们。在下面的代码中,我们将:
- 导入Pandas库。
- 读取
tweets.csv
到一个Pandas DataFrame种。 - 打印出该DataFrame的前
5
行。
import pandas as pd
tweets = pd.read_csv("tweets.csv")
tweets.head()
| id | id_str | user_location | user_bg_color | retweet_count | user_name | polarity | created | geo | user_description | user_created | user_followers | coordinates | subjectivity | text
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---
0 | 1 | 729828033092149248 | Wheeling WV | 022330 | 0 | Jaybo26003 | 0.00 | 2016-05-10T00:18:57 | NaN | NaN | 2011-11-17T02:45:42 | 39 | NaN | 0.0 | Make a difference vote! WV Bernie Sanders Coul...
1 | 2 | 729828033092161537 | NaN | C0DEED | 0 | brittttany_ns | 0.15 | 2016-05-10T00:18:57 | NaN | 18 // PSJAN | 2012-12-24T17:33:12 | 1175 | NaN | 0.1 | RT @HlPHOPNEWS: T.I. says if Donald Trump wins...
2 | 3 | 729828033566224384 | NaN | C0DEED | 0 | JeffriesLori | 0.00 | 2016-05-10T00:18:57 | NaN | NaN | 2012-10-11T14:29:59 | 42 | NaN | 0.0 | You have no one to blame but yourselves if Tru...
3 | 4 | 729828033893302272 | global | C0DEED | 0 | WhorunsGOVs | 0.00 | 2016-05-10T00:18:57 | NaN | Get Latest Global Political news as they unfold | 2014-02-16T07:34:24 | 290 | NaN | 0.0 | 'Ruin the rest of their lives': Donald Trump c...
4 | 5 | 729828034178482177 | California, USA | 131516 | 0 | BJCG0830 | 0.00 | 2016-05-10T00:18:57 | NaN | Queer Latino invoking his 1st amendment privil... | 2009-03-21T01:43:26 | 354 | NaN | 0.0 | RT @elianayjohnson: Per source, GOP megadonor ...
下面是该数据中重要列的简要说明:
id
– 在数据库中行的id(这并不重要)。id_str
– Twitter上推特的id。user_location
– 推特用户在他们的Twitter信息中指定的位置。user_bg_color
– 推特用户简介的背景色。user_name
– 推特用户的Twitter用户名。polarity
– 推特的情感,从-1
到1
。1
表示非常积极,-1
表示非常消极。created
– 推特发送时间user_description
– 推特用户在其简介中指定的描述。user_created
– 推特账号创建时间。user_follower
– 该推特的关注人数。text
– 推特的文本。subjectivity
– 推特的主观性和客观性。0
表示非常可观,1
表示非常主观。
我们可以用这个数据集进行的最有趣的事情包括,比较关于一个候选人的推特和另一个候选人的推特。例如,我们可以比较关于Donald Trump的推特的客观性和关于Bernie Sanders的推特的客观性。
为了完成这个任务,我们首先需要生成一个列,该列表示每条推特提到了哪个候选人。在下面的代码中,我们将:
- 创建一个函数,查找在一段文字中,哪个候选人的名字出现了。
- 在DataFrames之上使用apply方法来生成一个名为
candidate
的新列,该列包括该推特提到了哪个(些)候选人。
def get_candidate(row):
candidates = []
text = row["text"].lower()
if "clinton" in text or "hillary" in text:
candidates.append("clinton")
if "trump" in text or "donald" in text:
candidates.append("trump")
if "sanders" in text or "bernie" in text:
candidates.append("sanders")
return ",".join(candidates)
tweets["candidate"] = tweets.apply(get_candidate,axis=1)
现在,我们准备好了。我们已经准备好使用matplotlib绘制第一张图。在matplotlib中,绘制一张图包括:
由于其灵活性,你可以在matplotlib中把多个图绘制在一张图片中。每一个Axes对象表示一张图,例如一个柱状图或直方图。
这可能听起来很复杂,但是matplotlib具有一些方便的方法,可以为我们完成建立一个Figure和Axes对象的工作。
为了使用matplotlib,首先,你讲需要使用import matplotlib.pyplot as plt
导入该库。如果你正使用Jupyter notebook,从而在该notebook内部设置使用matplotlib。
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
我们导入matplotlib.pyplot
,因为这包含matplotlib的绘图函数。为了方便,我们重命名它为plt
,因此可以更快绘图。
一旦我们导入了matplotlib,我们就可以绘制一张关于每个候选人被提到的推特数的柱状图。为了完成这点,我们将:
- 使用Pandas Series上的value_counts函数来统计每个候选人有多少条提及他的推特。
- 使用
plt.bar
来创建一个柱状图。我们将传递取值从0
到candidate
列的唯一值数目的数字列表,作为x轴输入,把计数当成y轴输入。 - 显示计数,从而我们拥有更多关于每一个柱子表示什么的上下文信息。
counts = tweets["candidate"].value_counts()
plt.bar(range(len(counts)), counts)
plt.show()
print(counts)
trump 119998
clinton,trump 30521
25429
sanders 25351
clinton 22746
clinton,sanders 6044
clinton,trump,sanders 4219
trump,sanders 3172
Name: candidate, dtype: int64
关于Trump的推特比关于Sanders或者Clinton的推特多得惊人!
你可能注意到,我们并没有创建Figure或者任何Axes对象。这是因为调用plt.bar
会自动设置一个Figure和一个Axes对象,表示该柱状图。调用plt.show方法会显示当前图表中的任何东西。在这种情况下,它显示一个包含了一个柱状图的图像。
在pyplot模块中,matplotlib有一些方法可以使得创建常见类型的图更快和更方便,因为它们自动创建一个Figure和一个Axes对象。最广泛使用的是:
- plt.bar – 创建一个柱状图。
- plt.boxplot – 创建一个盒形图和须状图。
- plt.hist – 创建一个直方图。
- plt.plot – 创建一个线条图。
- plt.scatter – 创建一个散点图。
调用任意这些方法将自动设置Figure和Axes对象,并且绘制图。这些方法的每一个都有不同的参数,可以传递它们来修改效果图。
现在,我们已经有了第一个基本的图,可以继续创建第二个更个性化的图了。我们会绘制一张基本的直方图,然后修改它,以添加标签及其他信息。
我们可以看的事情之一就是发推特的用户账号年龄。我们可以找到发关于Trump的推特的用户账号和发关于Clinton的推特的用户账号的创建时间之间是否有区别。拥有更多最近创建的用户账号的候选人可能意味着使用假账号进行某种Twitter操纵。
在下面的代码中,我们会:
- 将
created
和user_created
列转换成Pandas datetime类型。 - 创建一个
user_age
列,表示从该账号创建后至今的天数。 - 创建用户年龄直方图。
- 显示该直方图。
from datetime import datetime
tweets["created"] = pd.to_datetime(tweets["created"])
tweets["user_created"] = pd.to_datetime(tweets["user_created"])
tweets["user_age"] = tweets["user_created"].apply(lambda x: (datetime.now() - x).total_seconds() / 3600 / 24 / 365)
plt.hist(tweets["user_age"])
plt.show()
我们可以添加标题和轴标签到matplotlib图中。完成这件事的通用方法是:
- plt.title – 添加标题到图上。
- plt.xlabel – 添加x轴标签。
- plt.ylabel – 添加y轴标签。
由于我们之前讨论到的所有方法,像bar
和hist
,都会在figure中自动创建一个Figure和一个Axes对象,因此当调用该方法时,这些标签将会被添加到Axes对象上。
我们可以用上面的方法添加标签到我们之前的直方图上。在下面的代码中,我们会:
- 生成我们之前完成的相同的直方图。
- 画一个标题到该直方图。
- 画一个x轴标签到该直方图上。
- 画一个y轴标签到该直方图上。
- 显示该图。
plt.hist(tweets["user_age"])
plt.title("Tweets mentioning candidates")
plt.xlabel("Twitter account age in years")
plt.ylabel("# of tweets")
plt.show()
现在的直方图可以很好的告诉我们所有的推特账户的注册年龄,但是它并没有根据候选人进行分类,这可能会更有趣。我们可以在hist
放中添加额外的选项,以创建一个叠加柱状图。
在下面的代码中,我们会:
- 生成三个Pandas series,每个只包含关于某个特定的候选人的推特的
user_age
数据。 - 通过调用
hist
方法,并添加额外的选项创建一个叠加直方图。- 指定一个列表作为输入将绘制三组柱状图。
- 指定
stacked=True
将叠加这三个条的集合。 - 增加
label
选项将为图例生成正确的标签。
- 调用plt.legend方法来在右上角绘制一个图例。
- 添加标题,x轴和y轴标签。
- 显示该图。
cl_tweets = tweets["user_age"][tweets["candidate"] == "clinton"]
sa_tweets = tweets["user_age"][tweets["candidate"] == "sanders"]
tr_tweets = tweets["user_age"][tweets["candidate"] == "trump"]
plt.hist([
cl_tweets,
sa_tweets,
tr_tweets
],
stacked=True,
label=["clinton", "sanders", "trump"]
)
plt.legend()
plt.title("Tweets mentioning each candidate")
plt.xlabel("Twitter account age in years")
plt.ylabel("# of tweets")
plt.show()
我们可以利用matplotlibs在图上绘制文本的能力来添加注释。注释指向图表的特定部分,让我们一个片段来描述一些东东。
在下面的代码中,我们会创建和上面一样的直方图,但是会调用plt.annotate方法来添加注释到图中。
plt.hist([
cl_tweets,
sa_tweets,
tr_tweets
],
stacked=True,
label=["clinton", "sanders", "trump"]
)
plt.legend()
plt.title("Tweets mentioning each candidate")
plt.xlabel("Twitter account age in years")
plt.ylabel("# of tweets")
plt.annotate('More Trump tweets', xy=(1, 35000), xytext=(2, 35000),
arrowprops=dict(facecolor='black'))
plt.show()
下面是传给annotate
的选项的行为描述:
xy
– 确定x
和y
坐标中箭头应该从哪里开始。xytext
– 确定x
和y
坐标中文本应该从哪里开始。arrowprops
– 指定箭头相关的选项,例如颜色。
正如你所见的,关于Trump的推特明显比其他候选人更多,但是在账号注册年龄上,看不出显著的差异。
目前为止,我们使用了一些方法,像plt.bar
和plt.hist
,它们会自动创建一个Figure对象和一个Axes对象。然而,当我们想获得关于图的更多控制时,我们可以显式创建这些对象。我们可能想要更多控制的场景之一是,当我们想要在同张图上并排放置多个图表。
通过调用plt.subplots方法,我们可以生成一个Figure和多个Axes对象。传递两个参数,nrows
和ncols
,它们定义在Figure中Axes对象的布局。例如,plt.subplots(nrows=2, ncols=2)
会生成 2x2
网格的Axes对象。plt.subplots(nrows=2, ncols=1)
会生成2x1
网格的Axes对象,然后将这两个Axes对象垂直堆积在一起。
每个Axes对象支持pyplot
中的大多数方法。例如,我们可以在一个Axes对象上调用bar
方法来生成一个柱状图。
我们将生成4
张图,用来那些发关于Trump推特的用户的Twitter背景色中的红色和蓝色的数量。这可能显示,确定为共和党派的推特用户是否更倾向于在他们的个人资料中使用红色。
首先,我们要生成两列,red
和blue
,用来表示在每个推特用户的个人资料背景中,每种颜色的多少,从0
到1
。
在下面的代码中,我们将:
- 使用
apply
方法来遍历user_bg_color
列中的每一行,然后提取其中的红色总数。 - 使用
apply
方法来遍历user_bg_color
列中的每一行,然后提取其中的蓝色总数。
import matplotlib.colors as colors
tweets["red"] = tweets["user_bg_color"].apply(lambda x: colors.hex2color('#{0}'.format(x))[0])
tweets["blue"] = tweets["user_bg_color"].apply(lambda x: colors.hex2color('#{0}'.format(x))[2])
一旦我们拥有了数据,我们就可以创建图。每张图将会是一个直方图,用以显示个人资料背景包含特定数量的蓝色或红色的推特用户数。
在下面的代码中,我们:
- 使用
subplots
方法生成一个Figure和多个Axes。Axes将作为数组返回。 - Axes在一个2x2 NumPy数组中返回。通过使用数组的flat属性,提取每个Axes对象。这为我们提供了
4
个Axes对象用以工作。 - 使用hist方法在第一个Axes中绘制一个直方图。
- 使用set_title方法,设置第一个Axes的标题为
Red in all backgrounds
。这与plt.title
功能一致。 - 使用hist方法在第二个Axes中绘制一个直方图。
- 使用set_title方法,设置第二个Axes的标题为
Red in Trump tweeters
。 - 使用hist方法在第三个Axes中绘制一个直方图。
- 使用set_title方法,设置第三个Axes的标题为
Blue in all backgrounds
。这与plt.title
功能一致。 - 使用hist方法在第四个Axes中绘制一个直方图。
- 使用set_title方法,设置第四个Axes标题为
Blue in Trump tweeters
。 - 调用plt.tight_layout方法来减少图间的填充并调整所有元素。
- 显示该图。
fig, axes = plt.subplots(nrows=2, ncols=2)
ax0, ax1, ax2, ax3 = axes.flat
ax0.hist(tweets["red"])
ax0.set_title('Red in backgrounds')
ax1.hist(tweets["red"][tweets["candidate"] == "trump"].values)
ax1.set_title('Red in Trump tweeters')
ax2.hist(tweets["blue"])
ax2.set_title('Blue in backgrounds')
ax3.hist(tweets["blue"][tweets["candidate"] == "trump"].values)
ax3.set_title('Blue in Trump tweeters')
plt.tight_layout()
plt.show()
Twitter有默认的个人资料背景颜色,我们或许应该移除它,这样才能通过消除噪音,以生成一个更准确的图。该演示是十六进制格式的,其中,#000000
是黑色,而#ffffff
是白色。
下面是如何查找背景颜色中的最常见的颜色:
tweets["user_bg_color"].value_counts()
C0DEED 108977
000000 31119
F5F8FA 25597
131516 7731
1A1B1F 5059
022330 4300
0099B9 3958
现在,我们可以删除三种最常见的颜色,然后只画出那些有唯一背景颜色的用户。下面的代码大多数我们之前做过的,但是我们会:
- 从
user_bg_color
中移除C0DEED
,000000
, 和F5F8FA
。 - 创建一个函数,不在最后一个图表中绘制逻辑。
- 绘制和前面
4
个图一样的图,除了user_bg_color
中最常见的颜色。
tc = tweets[~tweets["user_bg_color"].isin(["C0DEED", "000000", "F5F8FA"])]
def create_plot(data):
fig, axes = plt.subplots(nrows=2, ncols=2)
ax0, ax1, ax2, ax3 = axes.flat
ax0.hist(data["red"])
ax0.set_title('Red in backgrounds')
ax1.hist(data["red"][data["candidate"] == "trump"].values)
ax1.set_title('Red in Trump tweets')
ax2.hist(data["blue"])
ax2.set_title('Blue in backgrounds')
ax3.hist(data["blue"][data["candidate"] == "trump"].values)
ax3.set_title('Blue in Trump tweeters')
plt.tight_layout()
plt.show()
create_plot(tc)
正如你所看到的,发布关于Trump的推特的用户的背景颜色中,红色和蓝色的分布几乎与所有推特用户的分布相同。
我们使用TextBlob,为每条推特生成情绪分值,存储在polarity
列中。我们可以为每个候选人绘制平均值以及标准偏差。标准偏差将会告诉我们在所有的推特之间,变化有多宽,而平均值将会告诉我们平均推特是什么样子的。
要这样做,我们可以添加2个Axes到单个Figure上,然后在一个中绘制polarity
平均值,在另一个中绘制标准偏差。由于在这些图中,有大量的文本标签,因此我们将需要增加生成的图像的大小来匹配。我们可以使用plt.subplots
方法中的figsize
选项来做到这点。
下面的代码将会:
- 根据候选人将推特进行分组,对于每个数值列(包括
polarity
),计算平均值和标准方差。 - 创建一个
7
x7
英寸的Figure,带2个Axes对象,垂直排列。 - 在第一个Axes对象上创建标准偏差的柱状图。
- 使用set_xticklabels方法设置刻度标记,使用
rotation
参数旋转标签45
度。 - 设置标题。
- 使用set_xticklabels方法设置刻度标记,使用
- 在第二个Axes对象上创建均值的柱状图。
- 设置刻度标记。
- 设置标题。
- 显示该图。
gr = tweets.groupby("candidate").agg([np.mean, np.std])
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(7, 7))
ax0, ax1 = axes.flat
std = gr["polarity"]["std"].iloc[1:]
mean = gr["polarity"]["mean"].iloc[1:]
ax0.bar(range(len(std)), std)
ax0.set_xticklabels(std.index, rotation=45)
ax0.set_title('Standard deviation of tweet sentiment')
ax1.bar(range(len(mean)), mean)
ax1.set_xticklabels(mean.index, rotation=45)
ax1.set_title('Mean tweet sentiment')
plt.tight_layout()
plt.show()
我们可以使用柱状图绘制根据候选人分组的推特长度。首先将推特分成short
, medium
, 和long
推特。然后计算提到每个候选人的推特落到每个组的个数。接着,生成并排每个候选人的条的柱状图。
要绘制推特长度,我们首先必须对这些推特进行分类,然后找出关于每个候选人的推特落入到每个箱中的个数。
下面的代码中,我们将:
- 定义一个函数,如果推特长度小于
100
个字符,将其标记为short
;如果在100
到135
个字符之间,将其标记为medium
;如果超过135
个字符,将其标记为long
。 - 使用
apply
来生成一个新的列tweet_length
。 - 找出关于每个候选人的推特落入到每个组中的个数。
def tweet_lengths(text):
if len(text) < 100:
return "short"
elif 100 <= len(text) <= 135:
return "medium"
else:
return "long"
tweets["tweet_length"] = tweets["text"].apply(tweet_lengths)
tl = {}
for candidate in ["clinton", "sanders", "trump"]:
tl[candidate] = tweets["tweet_length"][tweets["candidate"] == candidate].value_counts()
现在,我们有了想要绘制的数据了,可以生成并排柱状图了。我们将使用bar
方法来在相同的轴上,为每个候选人绘制推特长度。然而,我们将使用一个偏移量来将所绘制的第二个和第三个候选人的条向右偏移。这将为我们提供三个分类区域,short
, medium
, 和long
,每个区域中,每个候选人有一个条。
下面的代码中,我们:
- 创建一个Figure和一个Axes对象。
- 为每个条定义
width
,.5
。 - 生成值序列
x
,即0
,2
,4
。每个值是一个分类,例如short
,medium
, 和long
,的起始。我们设置每个分类之间的距离为2
,这样多个条之间就有空间了。 - 在Axes对象上绘制
clinton
推特,条位于x
定义的位置上。 - 在Axes对象上绘制
sanders
推特,但是添加width
到x
上,使得条移动到右方。 - 在Axes对象上绘制
trump
推特,但是添加width * 2
到x
上,使得条移动到更右方。 - 设置轴标签和标题。
- 使用
set_xticks
将刻度标记移动到每个分类区域的中心。 - 设置刻度标记。
fig, ax = plt.subplots()
width = .5
x = np.array(range(0, 6, 2))
ax.bar(x, tl["clinton"], width, color='g')
ax.bar(x + width, tl["sanders"], width, color='b')
ax.bar(x + (width * 2), tl["trump"], width, color='r')
ax.set_ylabel('# of tweets')
ax.set_title('Number of Tweets per candidate by length')
ax.set_xticks(x + (width * 1.5))
ax.set_xticklabels(('long', 'medium', 'short'))
ax.set_xlabel('Tweet length')
plt.show()
我们已经学到了很多关于matplotlib生成图的知识,以及仔细好好看了该数据集。如果你想要阅读更多关于matplotlib内部如何绘制的内容,阅读这里。
接下来,你可以绘制很多的图:
- 分析用户描述,看看描述长度怎样因候选人而不同。
- 浏览当天时间 —— 某个候选人的支持者在某个特定时间会发更多推特吗?
- 探索用户位置,看看哪个州发关于哪个候选人最多的推特。
- 看看什么样子的用户名发关于哪个候选人最多的推特。
- 用户名中更多的数字是否与某个候选人相关联?
- 哪个候选人拥有最多的全大写用户名的支持者?
- 抓取更多的数据,看看模式是否转变。
希望这个matplotlib教程有用,如果你对这个数据做了任何好玩的分析,在下面(Ele注:去原文哈)留言吧 —— 我们很想知道!