动动发财的小手,点个赞吧!
曾几何时,我们很多人都遇到过这个问题。除非您有天赋或者之前碰巧参加过设计课程,否则制作同时对观众直观的视觉美学图表可能非常具有挑战性且耗时。
当时我的想法是:我想更加有意识地制作图表,以便直观地向观众传达信息。我的意思是,不要仅仅为了理解正在发生的事情而过度消耗他们的脑力和时间。
我曾经认为从 Matplotlib 切换到 Seaborn,最后切换到 Plotly 可以解决美学问题。确实,我错了。可视化不仅仅是美学。下面是我试图从 Cole Nussbaumer Knaflic 的《用数据讲故事》中复制两个可视化,它们真正激励我改变我的可视化方法。它们看起来干净、优雅、目标明确。我们将尝试在本文中复制这些图表!
这是这篇文章的要点。如果您正在寻找对出色的可视化背后的概念的深入解释,请查看“用数据讲故事”,每一页都是值得您花时间的瑰宝。如果您正在寻找特定于工具的实用建议,那么您来对地方了。 Cole 在书的开头提到,她提出的建议是通用的且与工具无关,尽管她承认书中的示例是使用 Excel 创建的。由于多种原因,有些人(包括我自己)不喜欢 Excel 和拖放工具。有些人喜欢使用 Python、R 和其他一些编程语言创建可视化。如果您属于此部分并使用 Python 作为主要工具,那么本文适合您。
链接——Pandas 图
如果您是使用 Pandas 进行数据整理的专家或经验丰富的玩家,您可能会遇到甚至采用“链接”的想法。简而言之,链接使您的代码更具可读性、更易于调试并且可以投入生产。这是我所指的一个简单示例。您不必逐行阅读,只需快速浏览即可了解“链接”背后的想法。每个步骤都清晰易懂,代码组织良好,没有不必要的中间变量。
(epl_10seasons
.rename(columns=lambda df_: df_.strip())
.rename(columns=lambda df_: re.sub('W+|[!,*)@#%(&$_?.^]', '_', df_))
.pipe(lambda df_: df_.astype({column: 'int8' for column in (df_.select_dtypes("integer").columns.tolist())}))
.pipe(lambda df_: df_.astype({column: 'category' for column in (df_.select_dtypes("object").columns.tolist()[:-1])}))
.assign(match_date=lambda df_: pd.to_datetime(df_.match_date, infer_datetime_format=True))
.assign(home_team=lambda df_: np.where((df_.home_team == "Arsenal"), "The Gunners", df_.home_team),
away_team=lambda df_: np.where((df_.away_team == "Arsenal"), "The Gunners", df_.away_team),
month=lambda df_: df_.match_date.dt.month_name())
.query('home_team == "The Gunners"')
)
这很棒,但是您知道您也可以继续链接过程来创建基本的可视化图表吗?默认情况下,Pandas Plot 使用 Matplotlib 后端来实现此目的。让我们看看它是如何工作的,并重现 Cole 在她的书中创建的一些示例。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
%matplotlib inline
pd.options.plotting.backend = 'plotly'
df = pd.DataFrame({"concerns": ["Engine power is less than expected",
"Tires make excessive noise while driving",
"Engine makes abnormal/excessive noise",
"Seat material concerns",
"Excessive wind noise",
"Hesitation or delay when shifting",
"Bluetooth system has poor sound quality",
"Steering system/wheel has too much play",
"Bluetooth system is difficult to use",
"Front seat audio/entertainment/navigation controls"
],
"concerns per 1,000": [12.9, 12.3, 11.6, 11.6, 11.0, 10.3, 10.0, 8.8, 8.6, 8.2],},
index=list(range(0,10,1)))
我们有一个如下所示的 DataFrame。
(df
.plot
.barh()
)
这是生成基本可视化图表的最快方法。通过直接从 DataFrame 链接 .plot 属性和 .line 方法,我们获得了下面的图。
如果您认为上面的情节没有通过美学检查,请保留您的反应和判断。事实上,至少可以说它看起来很丑。让我们来调味并做得更好。诀窍是,将 Pandas 绘图后端从 Matplotlib 切换到 Plotly,以获得即将解开的魔力。
pd.options.plotting.backend = 'plotly'
你可能会问,“为什么我要把它改成 Plotly? Matplotlib 不能做同样的事情吗?”嗯,这就是区别。
如果我们在 Pandas 中使用 Matplotlib 后端,它会返回一个 Axes 对象,请尝试使用内置 type() 方法进行验证。这很棒,因为坐标区对象允许我们访问方法来进一步修改图表。查看此文档²,了解在 Axes 对象上执行的可能方法。让我们快速选一个来说明。
(df
.plot
.barh()
.set_xlabel("concerns per 1,000")
)
我们成功地将 x 轴标签设置为“每 1,000 个关注点”,但这样做时,我们返回了一个 Text 对象并丢失了宝贵的 Axis 对象,该对象允许我们访问宝贵的方法来进一步修改图表。太糟糕了!
这是解决上述限制的另一种方法,
(df
.plot
.barh(xlabel="Concerns per 1,000", ylabel="Concerns", title="Top 10 design concerns")
)
然而,我们仍然无法进行广泛的修改,因为 Pandas 的实现非常限制集成。
另一方面,Plotly 不返回 Axes 对象。它返回一个 go.Figure 对象。此处的区别在于,负责更新图表的方法还会返回一个 go.Figure 对象,该对象允许您继续链接方法以进一步更新图表。让我们尝试一下吧!
顺便说一句,如果您想知道我如何获得下面的方法和参数的组合,它们都可以在此处的官方文档中找到。
以下是一些帮助您入门的重要方法 - .update_traces、.add_traces、.update_layout、.update_xaxes、.update_yaxes、.add_annotation、.update_annotations。
水平条形图
让我们为下面的可视化定义一组调色板。
GRAY1, GRAY2, GRAY3 = '#231F20', '#414040', '#555655'
GRAY4, GRAY5, GRAY6 = '#646369', '#76787B', '#828282'
GRAY7, GRAY8, GRAY9, GRAY10 = '#929497', '#A6A6A5', '#BFBEBE', '#FFFFFF'
BLUE1, BLUE2, BLUE3, BLUE4, BLUE5 = '#25436C', '#174A7E', '#4A81BF', '#94B2D7', '#94AFC5'
BLUE6, BLUE7 = '#92CDDD', '#2E869D'
RED1, RED2, RED3 = '#B14D4A', '#C3514E', '#E6BAB7'
GREEN1, GREEN2 = '#0C8040', '#9ABB59'
ORANGE1, ORANGE2, ORANGE3 = '#F36721', '#F79747', '#FAC090'
gray_palette = [GRAY1, GRAY2, GRAY3, GRAY4, GRAY5, GRAY6, GRAY7, GRAY8, GRAY9, GRAY10]
blue_palette = [BLUE1, BLUE2, BLUE3, BLUE4, BLUE5, BLUE6, BLUE7]
red_palette = [RED1, RED2, RED3]
green_palette = [GREEN1, GREEN2]
orange_palette = [ORANGE1, ORANGE2, ORANGE3]
sns.set_style("darkgrid")
sns.set_palette(gray_palette)
sns.palplot(sns.color_palette())
在这里,我们希望通过定义单独的颜色来突出显示等于或高于 10% 的问题。
color = np.array(['rgb(255,255,255)']*df.shape[0])
color[df
.set_index("concerns", drop=True)
.iloc[::-1]
["concerns per 1,000"]>=10] = red_palette[0]
color[df
.set_index("concerns", drop=True)
.iloc[::-1]
["concerns per 1,000"]