关于程序员:Plotly-和-Pandas强强联手实现有效的数据可视化

6次阅读

共计 13402 个字符,预计需要花费 34 分钟才能阅读完成。

动动发财的小手,点个赞吧!

曾几何时,咱们很多人都遇到过这个问题。除非您有天才或者之前碰巧加入过设计课程,否则制作同时对观众直观的视觉美学图表可能十分具备挑战性且耗时。

过后我的想法是:我想更加无意识地制作图表,以便直观地向观众传播信息。我的意思是,不要仅仅为了了解正在产生的事件而适度耗费他们的脑力和工夫。

我已经认为从 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"]<10] = gray_palette[4]

而后咱们间接从 DataFrame 创立绘图。

(df
 .set_index("concerns", drop=True)
 .iloc[::-1]
 .plot
 .barh()
 .update_traces(marker=dict(color=color.tolist()))
)

更新布局会产生以下后果。在这里,咱们指定模板,向绘图增加题目和边距,并指定图形对象的大小。咱们临时评论一下正文。

(df
 .set_index("concerns", drop=True)
 .iloc[::-1]
 .plot
 .barh()
 .update_traces(marker=dict(color=color.tolist()))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Top 10 design concerns</b> <br><sup><i>concerns per 1,000</i></sup>", 
                           font_size=30,
                           font_color=gray_palette[4]),
                margin=dict(l=50,
                            r=50,
                            b=50,
                            t=100,
                            pad=20),
                width=1000, 
                height=800, 
                showlegend=False, 
                #annotations=annotations
               )
)

更新 x 和 y 轴属性会产生以下后果。

(df
 .set_index("concerns", drop=True)
 .iloc[::-1]
 .plot
 .barh()
 .update_traces(marker=dict(color=color.tolist()))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Top 10 design concerns</b> <br><sup><i>concerns per 1,000</i></sup>", 
                           font_size=30,
                           font_color=gray_palette[4]),
                margin=dict(l=50,
                            r=50,
                            b=50,
                            t=100,
                            pad=20),
                width=1000, 
                height=800, 
                showlegend=False, 
                #annotations=annotations
               )
 .update_xaxes(title_standoff=10,
               showgrid=False,
               visible=False,
               tickfont=dict(
                        family='Arial',
                        size=16,
                        color=gray_palette[4],),
               title="")
 .update_yaxes(title_standoff=10,
               tickfont=dict(
                        family='Arial',
                        size=16,
                        color=gray_palette[4],),
               title="")
)

最初但并非最不重要的一点是,咱们将在图表中增加一些正文。在这里,咱们有一些正文 – 将数据标签增加到程度条形图和脚注。让咱们一起来做吧。首先,咱们在独自的单元格上定义正文。

annotations = []

y_s = np.round(df["concerns per 1,000"], decimals=2)

# Adding data labels
for yd, xd in zip(y_s, df.concerns):
    # labeling the bar net worth
    annotations.append(dict(xref='x1', 
                            yref='y1',
                            y=xd, x=yd - 1,
                            text=str(yd) + '%',
                            font=dict(family='Arial', size=16,
                                      color=gray_palette[-1]),
                            showarrow=False))
    
# Adding Source Annotations
annotations.append(dict(xref='paper', 
                        yref='paper',
                        x=-0.72, 
                        y=-0.050,
                        text='Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco'
                             '<br>laboris nisi ut aliquip ex ea commodo consequat.',
                        font=dict(family='Arial', size=10, color=gray_palette[4]),
                        showarrow=False,
                        align='left'))
(df
 .set_index("concerns", drop=True)
 .iloc[::-1]
 .plot
 .barh()
 .update_traces(marker=dict(color=color.tolist()))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Top 10 design concerns</b> <br><sup><i>concerns per 1,000</i></sup>", 
                           font_size=30,
                           font_color=gray_palette[4]),
                margin=dict(l=50,
                            r=50,
                            b=50,
                            t=100,
                            pad=20),
                width=1000, 
                height=800, 
                showlegend=False, 
                annotations=annotations
               )
 .update_xaxes(title_standoff=10,
               showgrid=False,
               visible=False,
               tickfont=dict(
                        family='Arial',
                        size=16,
                        color=gray_palette[4],),
               title="")
 .update_yaxes(title_standoff=10,
               tickfont=dict(
                        family='Arial',
                        size=16,
                        color=gray_palette[4],),
               title="")
)

绝对于最后的默认版本,这不是一个更好的图表吗?让咱们持续摸索另一种风行的图表——折线图。

请留神,上面的示例比下面的示例更简单。尽管如此,这个想法依然是一样的。

折线图

让咱们疾速浏览一下折线图的默认 Matplotlib 绘图后端。

pd.options.plotting.backend = 'matplotlib'

df = pd.DataFrame({"Received": [160,184,241,149,180,161,132,202,160,139,149,177],
                   "Processed":[160,184,237,148,181,150,123,156,126,104,124,140]},
                  index=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])

(df
 .plot
 .line());

让咱们将绘图后端切换到 Plotly!

pd.options.plotting.backend = 'plotly'

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
)

将 Pandas 绘图后端切换到 Plotly 后,下面的代码给出了以下内容。在这里,咱们首先仅绘制已接管序列。

让咱们通过进一步链接下面的办法来更新线属性。在这里,咱们批改色彩、宽度并在数据点处搁置标记。

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
)

让咱们将解决后的系列增加到图表中!

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
 .add_traces(go.Scatter(x=df.index, #Add Processed col
                        y=df.Processed, 
                        mode="lines+markers+text", 
                        line={"color": blue_palette[0], "width":4},
                        marker=dict(size=12)))
)

让咱们在索引 May 处增加一条垂直线,以显示两条线开始一致的点。

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
 .add_traces(go.Scatter(x=df.index, #Add Processed col
                        y=df.Processed, 
                        mode="lines+markers+text", 
                        line={"color": blue_palette[0], "width":4},
                        marker=dict(size=12)))
 .add_traces(go.Scatter(x=["May", "May"], #Add vline
                        y=[0,230], 
                        fill="toself", 
                        mode="lines", 
                        line_width=0.5, 
                        line_color= gray_palette[4]))
)

接下来,让咱们更新整体布局,将背景更改为红色,并增加题目、边距和一些其余元素。对于正文,咱们临时正文掉。

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
 .add_traces(go.Scatter(x=df.index, #Add Processed col
                        y=df.Processed, 
                        mode="lines+markers+text", 
                        line={"color": blue_palette[0], "width":4},
                        marker=dict(size=12)))
 .add_traces(go.Scatter(x=["May", "May"], #Add vline
                        y=[0,230], 
                        fill="toself", 
                        mode="lines", 
                        line_width=0.5, 
                        line_color= gray_palette[4]))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Please approve the hire of 2 FTEs</b> <br><sup>to backfill those who quit in the past year</sup> <br>Ticket volume over time <br><br><br>", 
                           font_size=30,),
                margin=dict(l=50,
                            r=50,
                            b=100,
                            t=200,),
                width=900, 
                height=700, 
                yaxis_range=[0, 300], 
                showlegend=False, 
                #annotations=right_annotations,
                )
)

接下来,咱们将对 x 轴和 y 轴执行更新。

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
 .add_traces(go.Scatter(x=df.index, #Add Processed col
                        y=df.Processed, 
                        mode="lines+markers+text", 
                        line={"color": blue_palette[0], "width":4},
                        marker=dict(size=12)))
 .add_traces(go.Scatter(x=["May", "May"], #Add vline
                        y=[0,230], 
                        fill="toself", 
                        mode="lines", 
                        line_width=0.5, 
                        line_color= gray_palette[4]))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Please approve the hire of 2 FTEs</b> <br><sup>to backfill those who quit in the past year</sup> <br>Ticket volume over time <br><br><br>", 
                           font_size=30,),
                margin=dict(l=50,
                            r=50,
                            b=100,
                            t=200,),
                width=900, 
                height=700, 
                yaxis_range=[0, 300], 
                showlegend=False, 
                #annotations=right_annotations,
                )
 .update_xaxes(dict(range=[0, 12],
                    showline=True,
                    showgrid=False,
                    linecolor=gray_palette[4],
                    linewidth=2,
                    ticks='',
                    tickfont=dict(
                        family='Arial',
                        size=13,
                        color=gray_palette[4],
                    ),
 ))
 .update_yaxes(dict(showline=True,
                    showticklabels=True,
                    showgrid=False,
                    ticks='outside',
                    linecolor=gray_palette[4],
                    linewidth=2,
                    tickfont=dict(
                        family='Arial',
                        size=13,
                        color=gray_palette[4],
                    ),
                    title_text="Number of tickets"
 ))
)

最初但并非最不重要的一点是,咱们将在图表中增加一些正文。在这里,咱们有一些正文 – 向折线图增加标签(已接管、已解决),以及向散点增加标签,这可能有点简单。让咱们一起来做吧。首先,咱们在独自的单元格上定义正文。

y_data = df.to_numpy()
colors = [gray_palette[3], blue_palette[0]]
labels = df.columns.to_list()

right_annotations = []

# Adding labels to line
for y_trace, label, color in zip(y_data[-1], labels, colors):

    right_annotations.append(dict(xref='paper', 
                                  x=0.95, 
                                  y=y_trace,
                                  xanchor='left', 
                                  yanchor='middle',
                                  text=label,
                                  font=dict(family='Arial',size=16,color=color),
                                  showarrow=False))

# Adding labels to scatter point
scatter_annotations = []

y_received = [each for each in df.Received]
y_processed  = [float(each) for each in df.Processed]
x_index = [each for each in df.index]
y_r = np.round(y_received)
y_p = np.rint(y_processed)

for ydn, yd, xd in zip(y_r[-5:], y_p[-5:], x_index[-5:]):

    scatter_annotations.append(dict(xref='x2 domain', 
                                    yref='y2 domain', 
                                    y=ydn, 
                                    x=xd, 
                                    text='{:,}'.format(ydn),
                                    font=dict(family='Arial',size=16,color=gray_palette[4]),
                                    showarrow=False,
                                    xanchor='center', 
                                    yanchor='bottom',
                                    ))
    
    scatter_annotations.append(dict(xref='x2 domain', 
                                    yref='y2 domain', 
                                    y=yd, 
                                    x=xd, 
                                    text='{:,}'.format(yd),
                                    font=dict(family='Arial',size=16,color=blue_palette[0]),
                                    showarrow=False,
                                    xanchor='center', 
                                    yanchor='top',
                                    ))

定义正文后,咱们只须要将正文变量放入链接办法中,如下所示。

(df
 .plot(x=df.index, 
       y=df.Received,
       labels=dict(index="", value="Number of tickets"),)
 .update_traces(go.Scatter(mode='lines+markers+text', 
                           line={"color": gray_palette[4], "width":4},
                           marker=dict(size=12)),)
 .add_traces(go.Scatter(x=df.index, #Add Processed col
                        y=df.Processed, 
                        mode="lines+markers+text", 
                        line={"color": blue_palette[0], "width":4},
                        marker=dict(size=12)))
 .add_traces(go.Scatter(x=["May", "May"], #Add vline
                        y=[0,230], 
                        fill="toself", 
                        mode="lines", 
                        line_width=0.5, 
                        line_color= gray_palette[4]))
 .update_layout(template="plotly_white",
                title=dict(text="<b>Please approve the hire of 2 FTEs</b> <br><sup>to backfill those who quit in the past year</sup> <br>Ticket volume over time <br><br><br>", 
                           font_size=30,),
                margin=dict(l=50,
                            r=50,
                            b=100,
                            t=200,),
                width=900, 
                height=700, 
                yaxis_range=[0, 300], 
                showlegend=False, 
                annotations=right_annotations,
                )
 .update_layout(annotations=scatter_annotations * 2)
 .update_xaxes(dict(range=[0, 12],
                    showline=True,
                    showgrid=False,
                    linecolor=gray_palette[4],
                    linewidth=2,
                    ticks='',
                    tickfont=dict(
                        family='Arial',
                        size=13,
                        color=gray_palette[4],
                    ),
 ))
 .update_yaxes(dict(showline=True,
                    showticklabels=True,
                    showgrid=False,
                    ticks='outside',
                    linecolor=gray_palette[4],
                    linewidth=2,
                    tickfont=dict(
                        family='Arial',
                        size=13,
                        color=gray_palette[4],
                    ),
                    title_text="Number of tickets"
 ))
 .add_annotation(dict(text="<b>2 employees quit in May.</b> We nearly kept up with incoming volume <br>in the following two months, but fell behind with the increase in Aug <br>and haven't been able to catch up since.",
                      font_size=18,
                      align="left",
                      x=7.5,
                      y=265,
                      showarrow=False))
 .add_annotation(dict(xref='paper', 
                        yref='paper',
                        x=0.5, 
                        y=-0.15,
                        text='Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco'
                             '<br>laboris nisi ut aliquip ex ea commodo consequat.',
                        font=dict(family='Arial',
                                  size=10,
                                  color='rgb(150,150,150)'),
                        showarrow=False,
                        align='left'))
 .update_annotations(yshift=0)
 .show())

本文由 mdnice 多平台公布

正文完
 0