跳至主要内容

Python 词云生成教程

学习如何使用 Python 的 WordCloud 进行自然语言处理的探索性数据分析。
· 11分钟 阅读

使用 AI 探索

在线运行和编辑此教程中的代码

运行代码

什么是词云?

您可能经常会看到一朵由大小不一的词汇构成的“云”,这些大小代表了每个词出现的频率或重要性。这就是 Tag Cloud(标签云)或词云。在本教程中,您将学习如何在 Python 中创建词云,并按需进行自定义。这个工具能帮助您探索文本数据,让报告更加生动。

通过这个动手练习在 Python 中练习生成词云。

在本教程中,我们将使用来自 Wine Enthusiast 网站的葡萄酒评论数据集来学习:

  • 如何从一个或多个文本文档创建基础词云
  • 调整词云中文字的颜色、大小与数量
  • 将词云遮罩为任意形状
  • 将词云映射为任意配色样式

何时使用词云

需要注意的是,词云适合用来可视化文本或数据集中常见的词,但通常只适合用于主题的高层概览。它们类似于条形图,但往往更具视觉吸引力(虽然有时更难解读)。词云在以下场景尤其有用:

  • 快速识别海量文本中的关键信息或主题
  • 理解一篇文章的整体情绪或基调
  • 探索包含文本信息的数据中的模式或趋势
  • 以更具视觉吸引力的方式传达关键观点或概念

不过要记住,词云并不提供词语和短语的上下文或更深入的理解。因此,最好与其他文本数据分析与解读方法配合使用。

前提条件

要在 Python 中开始制作词云,您需要安装以下一些包:

numpy 是最常用、最有用的库之一,用于处理多维数组和矩阵。它也常与 pandas 搭配进行数据分析。

Python 的 os 模块是内置库,无需安装。若想进一步了解如何用 os 模块处理文件,建议阅读 DataCamp 的这篇Python 读写文件教程。

在可视化方面,matplotlib 是一个基础库,许多其他库(包括您在本教程中将用到的 seaborn 或 wordcloud)都基于它进行绘图。pillow 用于读取图像。Pillow 是 PIL(Python Imaging Library)的封装。您需要这个库来读入图像作为词云的遮罩。

wordcloud 的安装可能稍有曲折。如果您只需要绘制基础词云,使用 pip install wordcloud 或 conda install -c conda-forge wordcloud 即可。然而,若想使用最新版本(支持将云遮罩为任意形状),需要按如下方式安装:

git clone https://github.com/amueller/word_cloud.git
cd word_cloud
pip install .

数据集:

本教程使用 Kaggle 的葡萄酒评论数据集。该数据集非常适合入门学习:没有缺失值(无需耗时处理),并且包含大量文本(葡萄酒评论)、分类和数值数据。

在 Python 中创建词云:准备工作 

安装必要的库

首先,加载所有必要的库:

# Start with loading all necessary libraries
import numpy as np
import pandas as pd
from os import path
from PIL import Image
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
import matplotlib.pyplot as plt
% matplotlib inline
c:\intelpython3\lib\site-packages\matplotlib\__init__.py:
import warnings
warnings.filterwarnings("ignore")

如果您导入的库超过 10 个,建议按类别组织(如基础库、可视化、模型等)。在代码中使用注释能让代码更清晰、更易于理解。

将数据加载到 DataFrame 

现在使用 pandas 的 read_csv 将数据加载为 DataFrame。注意参数 index_col=0,表示不将行名(索引)读作单独一列。

# Load in the dataframe
df = pd.read_csv("data/winemag-data-130k-v2.csv", index_col=0)
# Looking at first 5 rows of the dataset
df.head()


dataset

您可以结合使用 print() 和 .format() 打印数据集的一些基本信息,获得更美观的输出。

print("There are {} observations and {} features in this dataset. \n".format(df.shape[0],df.shape[1]))

print("There are {} types of wine in this dataset such as {}... \n".format(len(df.variety.unique()),
                                                                           ", ".join(df.variety.unique()[0:5])))

print("There are {} countries producing wine in this dataset such as {}... \n".format(len(df.country.unique()),
                                                                                      ", ".join(df.country.unique()[0:5])))
There are 129971 observations and 13 features in this dataset.

There are 708 types of wine in this dataset such as White Blend, Portuguese Red, Pinot Gris, Riesling, Pinot Noir...

There are 44 countries producing wine in this dataset such as Italy, Portugal, US, Spain, France...
df[["country", "description","points"]].head()
 countrydescriptionpoints
0ItalyAromas include tropical fruit, broom, brimston...87
1PortugalThis is ripe and fruity, a wine that is smooth...87
2USTart and snappy, the flavors of lime flesh and...87
3USPineapple rind, lemon pith and orange blossom ...87
4USMuch like the regular bottling from 2012, this...87

创建分组以比较特征

若要比较某个特征的不同分组,您可以使用 groupby() 并计算汇总统计。

对该葡萄酒数据集,您可以按国家分组,查看各国评分和价格的汇总统计,或筛选出最受欢迎和最昂贵的那些。

# Groupby by country
country = df.groupby("country")

# Summary statistic of all countries
country.describe().head()


dataset

下面选出 44 个国家中平均评分最高的前 5 名:

country.mean().sort_values(by="points",ascending=False).head()
countrypointsprice
England91.58108151.681159
India90.22222213.333333
Austria90.10134530.762772
Germany89.85173242.257547
Canada89.36965035.712598

绘制数据

您可以使用 Pandas DataFrame 的绘图方法配合 Matplotlib,按国家绘制葡萄酒数量的图。如果您不熟悉 Matplotlib,可以快速浏览我们的Matplotlib 教程。

plt.figure(figsize=(15,10))
country.size().sort_values(ascending=False).plot.bar()
plt.xticks(rotation=50)
plt.xlabel("Country of Origin")
plt.ylabel("Number of Wines")
plt.show()


graph

在 44 个产酒国家中,美国在该数据集中拥有超过 5 万种葡萄酒,是排名第二的法国(以葡萄酒闻名)的两倍。意大利也产出大量优质葡萄酒,接近 2 万种酒可供评论。

检查数据

现在我们按与上面相同的绘图方法,看看 44 个国家各自评分最高的葡萄酒:

plt.figure(figsize=(15,10))
country.max().sort_values(by="points",ascending=False)["points"].plot.bar()
plt.xticks(rotation=50)
plt.xlabel("Country of Origin")
plt.ylabel("Highest point of Wines")
plt.show()


graph

澳大利亚、美国、葡萄牙、意大利和法国都有 100 分的葡萄酒。您会注意到,葡萄牙在数据集中按产量排名第 5,澳大利亚排名第 9,两国的葡萄酒种类都少于 8000。
以上是对本教程所用数据的一点初步探索。接下来开始进入“主菜”:词云。

在 Python 中设置基础词云

开始上手

词云是一种展示给定文本中高频词的可视化技术。我们可以使用一个Python 库来实现。在使用任何函数之前,您可能首先想查看函数的文档字符串,了解所有必填和可选参数。为此,在交互环境中输入 ?function 并运行即可查看。

?WordCloud
Init signature: WordCloud(font_path=None, width=400, height=200, margin=2, ranks_only=None, prefer_horizontal=0.9, mask=None, scale=1, color_func=None, max_words=200, min_font_size=4, stopwords=None, random_state=None, background_color='black', max_font_size=None, font_step=1, mode='RGB', relative_scaling=0.5, regexp=None, collocations=True, colormap=None, normalize_plurals=True, contour_width=0, contour_color='black')
Docstring:     
Word cloud object for generating and drawing.

Parameters
----------
font_path : string
    Font path to the font that will be used (OTF or TTF).
    Defaults to DroidSansMono path on a Linux machine. If you are on
    another OS or don't have this font; you need to adjust this path.

width : int (default=400)
    Width of the canvas.

height : int (default=200)
    Height of the canvas.

prefer_horizontal : float (default=0.90)
    The ratio of times to try horizontal fitting as opposed to vertical.
    If prefer_horizontal < 1, the algorithm will try rotating the word
    if it doesn't fit. (There is currently no built-in way to get only
    vertical words.)

mask : nd-array or None (default=None)
    If not None, gives a binary mask on where to draw words. If mask is not
    None, width and height will be ignored, and the shape of mask will be
    used instead. All white (#FF or #FFFFFF) entries will be considered
    "masked out" while other entries will be free to draw on. [This
    changed in the most recent version!]

contour_width: float (default=0)
    If mask is not None and contour_width > 0, draw the mask contour.

contour_color: color value (default="black")
    Mask contour color.

scale : float (default=1)
    Scaling between computation and drawing. For large word-cloud images,
    using scale instead of larger canvas size is significantly faster, but
    might lead to a coarser fit for the words.

min_font_size : int (default=4)
    Smallest font size to use. Will stop when there is no more room in this
    size.

font_step : int (default=1)
    Step size for the font. font_step > 1 might speed up computation but
    give a worse fit.

max_words : number (default=200)
    The maximum number of words.

stopwords : set of strings or None
    The words that will be eliminated. If None, the build-in STOPWORDS
    list will be used.

background_color : color value (default="black")
    Background color for the word cloud image.

max_font_size : int or None (default=None)
    Maximum font size for the largest word. If None, the height of the image is
    used.

mode : string (default="RGB")
    Transparent background will be generated when mode is "RGBA" and
    background_color is None.

relative_scaling : float (default=.5)
    Importance of relative word frequencies for font-size.  With
    relative_scaling=0, only word-ranks are considered.  With
    relative_scaling=1, a word that is twice as frequent will have twice
    the size.  If you want to consider the word frequencies and not only
    their rank, relative_scaling around .5 often looks good.

    .. versionchanged: 2.0
        Default is now 0.5.

color_func : callable, default=None
    Callable with parameters word, font_size, position, orientation,
    font_path, random_state that returns a PIL color for each word.
    Overwrites "colormap".
    See colormap for specifying a matplotlib colormap instead.

regexp : string or None (optional)
    Regular expression to split the input text into tokens in process_text.
    If None is specified, ``r"\w[\w']+"`` is used.

collocations : bool, default=True
    Whether to include collocations (bigrams) of two words.

    .. versionadded: 2.0

colormap : string or matplotlib colormap, default="viridis"
    Matplotlib colormap to randomly draw colors from for each word.
    Ignored if "color_func" is specified.

    .. versionadded: 2.0

normalize_plurals : bool, default=True
    Whether to remove trailing 's' from words. If True and a word
    appears with and without a trailing 's', the one with trailing 's'
    is removed and its counts are added to the version without
    trailing 's' -- unless the word ends with 'ss'.

Attributes
----------
``words_`` : dict of string to float
    Word tokens with associated frequency.

    .. versionchanged: 2.0
        ``words_`` is now a dictionary

``layout_`` : list of tuples (string, int, (int, int), int, color))
    Encodes the fitted word cloud. Encodes for each word the string, font
    size, position, orientation, and color.

Notes
-----
Larger canvases will make the code significantly slower. If you need a
large word cloud, try a lower canvas size, and set the scale parameter.

The algorithm might give more weight to the ranking of the words
then their actual frequencies, depending on the ``max_font_size`` and the
scaling heuristic.
File:           c:\intelpython3\lib\site-packages\wordcloud\wordcloud.py
Type:           type

您可以看到,WordCloud 对象唯一的必填参数是 text,其余参数均为可选。

从一个简单输入开始

我们从一个简单示例开始:将第一条评论的描述作为词云的输入。三步如下:

  • 提取评论(文本文档)
  • 创建并生成词云图像
  • 使用 matplotlib 显示词云
# Start with one review:
text = df.description[0]

# Create and generate a word cloud image:
wordcloud = WordCloud().generate(text)

# Display the generated image:
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()

wordcloud

很好!您可以看到,第一条评论多次提到了风干风味和酒的香气。

修改可选参数

现在更改一些可选参数,如 max_font_size、max_word 和 background_color。

# lower max_font_size, change the maximum number of word and lighten the background:
wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white").generate(text)
plt.figure()
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis("off")
plt.show()

wordcloud

看起来此处使用 max_font_size 可能不是好主意。它让不同词频之间的差异更难辨别。不过,调亮背景能让词云更易读。

如果您想保存图像,WordCloud 提供了 to_file 函数

# Save the image in the img folder:
wordcloud.to_file("img/first_review.png")
<wordcloud.wordcloud.WordCloud at 0x16f1d704978>

加载后结果如下所示:

wordcloud

您可能注意到 plt.imshow() 中的参数 interpolation="bilinear",用于让显示的图像更平滑。若想了解更多选择依据,这篇关于imshow 的插值方法教程是很好的资料。

合并数据

现在我们将所有葡萄酒评论合并为一个大文本,生成一个“大而全”的词云,看看这些葡萄酒最常见的特征。

text = " ".join(review for review in df.description)
print ("There are {} words in the combination of all review.".format(len(text)))
There are 31661073 words in the combination of all review.
# Create stopword list:
stopwords = set(STOPWORDS)
stopwords.update(["drink", "now", "wine", "flavor", "flavors"])

# Generate a word cloud image
wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(text)

# Display the generated image:
# the matplotlib way:
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()
wordcloud

看起来黑樱桃和饱满(full-bodied)是最常被提及的特征,而赤霞珠(Cabernet Sauvignon)是其中最受欢迎的。这与这样一个事实相符:赤霞珠“是世界上最广为人知的红葡萄酒葡萄品种之一。它几乎在所有主要葡萄酒生产国、跨越多样的气候带种植,从加拿大的奥肯那根谷到黎巴嫩的贝卡谷地”。[1]

Python 中的词云可视化

现在,让我们把这些词“倒进”一个酒杯(甚至一只酒瓶)里!

要为词云创建形状,首先需要找到一个 PNG 文件作为遮罩。下面这个就是网上可用的一个不错示例:

mask

并非所有遮罩图像的格式都相同,可能导致结果不同,甚至使 WordCloud 函数无法正常工作。为确保您的遮罩可用,我们先以 numpy 数组形式查看它:

wine_mask = np.array(Image.open("img/wine_mask.png"))
wine_mask
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)

遮罩函数的工作方式要求遮罩中所有白色部分应为 255 而不是 0(整数类型)。该值代表像素“强度”。255 表示纯白,1 表示黑色。这里,若您的遮罩与上面格式相同,可使用下方提供的函数对其进行转换。请注意,如果您的遮罩背景不是 0,而是 1 或 2,请相应调整函数以匹配实际情况。

首先,使用 transform_format() 函数将 0 替换为 255。

def transform_format(val):
    if val == 0:
        return 255
    else:
        return val

然后,创建一个与现有遮罩相同形状的新遮罩,并将 transform_format() 应用于旧遮罩每一行的每个值。

# Transform your mask into a new one that will work with the function:
transformed_wine_mask = np.ndarray((wine_mask.shape[0],wine_mask.shape[1]), np.int32)

for i in range(len(wine_mask)):
    transformed_wine_mask[i] = list(map(transform_format, wine_mask[i]))

现在您得到了正确格式的新遮罩。打印转换后的遮罩是检查函数是否正常工作的最佳方式。

# Check the expected result of your mask
transformed_wine_mask
array([[255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       ...,
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255],
       [255, 255, 255, ..., 255, 255, 255]])

有了正确的遮罩,您就可以开始用所选形状生成词云了。注意在 WordCloud 函数中有一个 mask 参数,用于接收上面创建的转换后遮罩。contour_width 和 contour_color 顾名思义,是用于调整词云轮廓特征的参数。这里的酒瓶是红酒瓶,因此 firebrick(火砖红)是个不错的轮廓颜色选择。更多颜色可参考这张命名颜色表。

# Create a word cloud image
wc = WordCloud(background_color="white", max_words=1000, mask=transformed_wine_mask,
               stopwords=stopwords, contour_width=3, contour_color='firebrick')

# Generate a wordcloud
wc.generate(text)

# store to file
wc.to_file("img/wine.png")

# show
plt.figure(figsize=[20,10])
plt.imshow(wc, interpolation='bilinear')
plt.axis("off")
plt.show()
wordcloud

搞定!您创建了一个酒瓶形状的词云!看起来,葡萄酒描述最常提到黑樱桃、水果风味和饱满的酒体。现在我们更仔细地查看各国评论,并使用各国国旗来绘制词云。下面是您很快就会做出的一个示例:

usa flag

按配色模式创建词云

您可以将葡萄酒数量最多的五个国家的所有评论合并。要找出这些国家,您可以查看上面的“国家 vs 数量”图,或使用先前的分组计算各国的观测数量,并配合 sort_values() 的 ascending=False 参数进行降序排序。

country.size().sort_values(ascending=False).head()
country
US          54504
France      22093
Italy       19540
Spain        6645
Portugal     5691
dtype: int64

因此,当前前 5 个国家是:美国、法国、意大利、西班牙和葡萄牙。您也可以通过在 head() 中指定数字来更改国家数量,例如:

country.size().sort_values(ascending=False).head(10)
country
US           54504
France       22093
Italy        19540
Spain         6645
Portugal      5691
Chile         4472
Argentina     3800
Austria       3345
Australia     2329
Germany       2165
dtype: int64

目前,五个国家已经足够了。

要获取每个国家的全部评论,您可以使用 " ".join(list) 语法将列表中的所有元素以空格连接起来。

# Join all reviews of each country:
usa = " ".join(review for review in df[df["country"]=="US"].description)
fra = " ".join(review for review in df[df["country"]=="France"].description)
ita = " ".join(review for review in df[df["country"]=="Italy"].description)
spa = " ".join(review for review in df[df["country"]=="Spain"].description)
por = " ".join(review for review in df[df["country"]=="Portugal"].description)

接着,您可以像上面一样创建词云。也可以将创建与生成两步合并,如下所示。颜色映射在绘制前通过 WordCloud 库中的 ImageColorGenerator 函数完成。

# Generate a word cloud image
mask = np.array(Image.open("img/us.png"))
wordcloud_usa = WordCloud(stopwords=stopwords, background_color="white", mode="RGBA", max_words=1000, mask=mask).generate(usa)

# create coloring from image
image_colors = ImageColorGenerator(mask)
plt.figure(figsize=[7,7])
plt.imshow(wordcloud_usa.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")

# store to file
plt.savefig("img/us_wine.png", format="png")

plt.show()
usa flag

效果不错!现在我们用法国的评论重复一遍。

# Generate a word cloud image
mask = np.array(Image.open("img/france.png"))
wordcloud_fra = WordCloud(stopwords=stopwords, background_color="white", mode="RGBA", max_words=1000, mask=mask).generate(fra)

# create coloring from image
image_colors = ImageColorGenerator(mask)
plt.figure(figsize=[7,7])
plt.imshow(wordcloud_fra.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")

# store to file
plt.savefig("img/fra_wine.png", format="png")

#plt.show()

请注意,应在绘制后再保存图像,以获得具有期望配色模式的词云。

france flag
# Generate a word cloud image
mask = np.array(Image.open("img/italy.png"))
wordcloud_ita = WordCloud(stopwords=stopwords, background_color="white", max_words=1000, mask=mask).generate(ita)

# create coloring from image
image_colors = ImageColorGenerator(mask)
plt.figure(figsize=[7,7])
plt.imshow(wordcloud_ita.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")

# store to file
plt.savefig("img/ita_wine.png", format="png")

#plt.show()
italy flag

接下来是西班牙:

# Generate a word cloud image
mask = np.array(Image.open("img/spain.png"))
wordcloud_spa = WordCloud(stopwords=stopwords, background_color="white", max_words=1000, mask=mask).generate(spa)

# create coloring from image
image_colors = ImageColorGenerator(mask)
plt.figure(figsize=[7,7])
plt.imshow(wordcloud_spa.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")

# store to file
plt.savefig("img/spa_wine.png", format="png")
#plt.show()
spain flag

最后是葡萄牙:

# Generate a word cloud image
mask = np.array(Image.open("img/portugal.png"))
wordcloud_por = WordCloud(stopwords=stopwords, background_color="white", max_words=1000, mask=mask).generate(por)

# create coloring from image
image_colors = ImageColorGenerator(mask)
plt.figure(figsize=[7,7])
plt.imshow(wordcloud_por.recolor(color_func=image_colors), interpolation="bilinear")
plt.axis("off")

# store to file
plt.savefig("img/por_wine.png", format="png")
#plt.show()
portugal flag

最终结果如下表所示,便于比较遮罩与词云。您最喜欢哪一个?

usa flag
france flag
italy flag
spain flag
portugal fflag

如何解读词云

现在我们已经看到了多个词云示例以及如何在 Python 中创建它们。接下来值得讨论的是如何解读这类数据可视化。一般而言,词云中每个词的大小代表它在文本中的频率或重要性。通常,出现越频繁的词,在词云中就越大。

解读词云时需要注意以下几点: 

  • 关注词的大小:如前所述,词的大小对应其在文本中的频率或重要性。因此,更大的词通常对文本的整体信息更关键。
  • 寻找模式:是否有一些词组经常一起出现?这可能表明文本中的重要主题或话题。
  • 结合上下文:请记住,词云只展示文本中出现的词,并不提供其含义或使用方式的信息。因此,要结合文本的上下文和词语的具体含义进行思考。
  • 警惕异常值:有时某个词可能因为出现频繁而在词云中显得很大,但它对文本整体信息未必有特别意义或相关性。留意这类“异常”,重点关注更具意义的词和模式。

总之,词云能帮助我们快速可视化文本中的关键主题与想法。但要记住,它只是众多文本分析工具中的一种,应与其他方法配合,用于更深入的分析与理解。

恭喜!

您做到了!您已经学会了使用 WordCloud 库在 Python 中绘制词云的多种方法,这将有助于任何文本分析的可视化。您也学会了如何将词云遮罩为任意形状,并应用任意配色。如果想继续练习技能,可以尝试 DataCamp 的项目:机器学习中最热门的话题

如果您对自然语言处理感兴趣,欢迎学习我们的Python 自然语言处理基础学习路径。

主题