Skip to content

Latest commit

 

History

History
529 lines (366 loc) · 25.9 KB

Matplotlib教程 - 绘制提到Trump, Clinton & Sanders的推特.md

File metadata and controls

529 lines (366 loc) · 25.9 KB

原文:Matplotlib tutorial: Plotting tweets mentioning Trump, Clinton & Sanders


使用Pandas和Matplotlib分析Tweets

Python有多种可视化库,包括seaborn, networkx, 和vispy。大多数的Python可视化库全部或部分基于matplotlib,这往往是绘制简单的图的第一种手段,也是绘制那些难以在其他库绘制的图的最后一种手段。

在这个matplotlib教程中,我们将介绍该库的基本知识,并看看如何进行一些中间可视化。

我们将使用包含将近240,000条关于Hillary Clinton, Donald Trump, 和Bernie Sanders,目前所有美国总统候选人的推特的数据集。

该数据是从Twitter Streaming API拉过来的,而所有240,000条推特的csv文件可以在这里下载。如果你想自己爬取更多数据,那么你可以看看这里的爬虫代码。

使用Pandas探索Tweets

在我们开始绘制之前,让我们加载数据并进行一些探索。我们可以使用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 – 推特的情感,从-111表示非常积极,-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中,绘制一张图包括:

  • 创建一个Figure来绘制图。
  • 创建一个或多个Axes对象来绘制该图。
  • 作为一个图像,显示该图表,以及其中的任何图。

由于其灵活性,你可以在matplotlib中把多个图绘制在一张图片中。每一个Axes对象表示一张图,例如一个柱状图或直方图。

这可能听起来很复杂,但是matplotlib具有一些方便的方法,可以为我们完成建立一个Figure和Axes对象的工作。

导入matplotlib

为了使用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来创建一个柱状图。我们将传递取值从0candidate列的唯一值数目的数字列表,作为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对象。最广泛使用的是:

调用任意这些方法将自动设置Figure和Axes对象,并且绘制图。这些方法的每一个都有不同的参数,可以传递它们来修改效果图。

自定义图

现在,我们已经有了第一个基本的图,可以继续创建第二个更个性化的图了。我们会绘制一张基本的直方图,然后修改它,以添加标签及其他信息。

我们可以看的事情之一就是发推特的用户账号年龄。我们可以找到发关于Trump的推特的用户账号和发关于Clinton的推特的用户账号的创建时间之间是否有区别。拥有更多最近创建的用户账号的候选人可能意味着使用假账号进行某种Twitter操纵。

在下面的代码中,我们会:

  • createduser_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图中。完成这件事的通用方法是:

由于我们之前讨论到的所有方法,像barhist,都会在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 – 确定xy坐标中箭头应该从哪里开始。
  • xytext – 确定xy坐标中文本应该从哪里开始。
  • arrowprops – 指定箭头相关的选项,例如颜色。

正如你所见的,关于Trump的推特明显比其他候选人更多,但是在账号注册年龄上,看不出显著的差异。

多个子图

目前为止,我们使用了一些方法,像plt.barplt.hist,它们会自动创建一个Figure对象和一个Axes对象。然而,当我们想获得关于图的更多控制时,我们可以显式创建这些对象。我们可能想要更多控制的场景之一是,当我们想要在同张图上并排放置多个图表。

通过调用plt.subplots方法,我们可以生成一个Figure和多个Axes对象。传递两个参数,nrowsncols,它们定义在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背景色中的红色和蓝色的数量。这可能显示,确定为共和党派的推特用户是否更倾向于在他们的个人资料中使用红色。

首先,我们要生成两列,redblue,用来表示在每个推特用户的个人资料背景中,每种颜色的多少,从01

在下面的代码中,我们将:

  • 使用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),计算平均值和标准方差。
  • 创建一个7x7英寸的Figure,带2个Axes对象,垂直排列。
  • 在第一个Axes对象上创建标准偏差的柱状图。
    • 使用set_xticklabels方法设置刻度标记,使用rotation参数旋转标签45度。
    • 设置标题。
  • 在第二个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推特。然后计算提到每个候选人的推特落到每个组的个数。接着,生成并排每个候选人的条的柱状图。

生成tweet长度

要绘制推特长度,我们首先必须对这些推特进行分类,然后找出关于每个候选人的推特落入到每个箱中的个数。

下面的代码中,我们将:

  • 定义一个函数,如果推特长度小于100个字符,将其标记为short;如果在100135个字符之间,将其标记为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推特,但是添加widthx上,使得条移动到右方。
  • 在Axes对象上绘制trump推特,但是添加width * 2x上,使得条移动到更右方。
  • 设置轴标签和标题。
  • 使用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注:去原文哈)留言吧 —— 我们很想知道!