如何制作一个“隆起图”

3

我有一张排名数据的表格,想要将其可视化为凸起图或斜坡图,例如:

我有一个绘制它的想法,但如果有一件事是我从pandas学到了的,那就是通常有某种组合的melt、merge、pivot和fiddle可以在一行代码中解决。也就是说,优雅的pandas,而不是混乱的pandas。

数据看起来有点像这样:(更多数据请点击这里)

版次名称 来源
2562 第3版 gq
2956 第8版 warontherocks
10168 第12版 aeon.co
1137 第14版 hbr.org
4573 第13版 thesmartnik
7143 第16版 vijayboyapati.medium
9674 第15版 medium
5555 第9版 smh.au
8831 第11版 salon
8215 第14版 thegospelcoalition.org

每一行均表示一篇文章,来源代表该文章的来源。目标是找出每个版本中哪些来源最多地贡献了文章。

这里是我尝试制作一个不太好的峰值图:

all_sources = set(sources)
source_rankings = {}
for s in all_sources:
    source_rankings[s]={}

for ed in printed.groupby("ed_name"):
    df = ed[1]
    vc = df.source.value_counts()
    for i, x in enumerate(vc.index):
        source_rankings[x][ed[0]] = i+1
ranks = pd.DataFrame(source_rankings)

cols_to_drop = []
for name, values in ranks.iteritems():
    interesting = any([x>30 for x in list(values) if not math.isnan(x)])
    # print(name, interesting)
    if interesting:
        cols_to_drop.append(name)
only_interesting = ranks.drop(labels=cols_to_drop, axis='columns')

only_interesting.sort_index(
    axis=0, inplace=True, 
    key=lambda col: [int(x.split("_")[1]) for x in col],
    ascending=False
    )

linestyles = ['-', '--', '-.', ':']

plt.plot(only_interesting, alpha=0.8, linewidth=1)
plt.ylim(25, 0)
plt.gca().invert_xaxis()
plt.xticks(rotation=70)
plt.title("Popularity of publisher by edition")

editions_that_rank_threshold = 10
for name, values in only_interesting.iteritems():
    if len(values[values.isna() == False]) > editions_that_rank_threshold: 
        for i, x in values.iteritems():
            if not math.isnan(x):
                # print(name, i, x)
                plt.annotate(xy=(i,x), text=name)
                plt.plot(values, linewidth=5, linestyle=sample(linestyles,1)[0])
                break

plt.xlabel("Edition")
plt.ylabel("Cardinal Rank (1 at the top)")
plt.close()

得到的结果大致如下:

enter image description here

可以说,这个结果远远不能令人满意。虽然使用标准的matplotlib方法可以解决很多问题,但我不愿这样做,因为它感觉不够优雅,而且可能会错过内置的bumpchart方法。

这个问题问了一个类似的问题,但是答案将其解决为斜率图。斜率图看起来很好,但那是另一种类型的图表。

有更优雅的方法吗?


1
mplsoccer库中有一个绘制“bump chart”的函数。希望这能对您有所帮助。 - r-beginners
2个回答

4

还有一个非常实用的GitHub代码库:https://github.com/kartikay-bagla/bump-plot-python

它基本上是一个类,可以让你从pd.DataFrame中绘制Bump图表。

data = {"A":[1,2,1,3],"B":[2,1,3,2],"C":[3,3,2,1]}
df = pd.DataFrame(data, index=['step_1','step_2','step_3','step_4'])

plt.figure(figsize=(10, 5))
bumpchart(df, show_rank_axis= True, scatter= True, holes= False,
          line_args= {"linewidth": 5, "alpha": 0.5}, scatter_args= {"s": 100, "alpha": 0.8}) ## bump chart class with nice examples can be found on github
plt.show()

bump chart example

免责声明。我不是这个仓库的创建者,但我发现它非常有帮助。


4

我认为你不需要寻找一些内置方法。我不确定你的数据是否适合用于“颠簸图”,因为版本间的差异似乎很大,而且几个来源似乎具有相同的排名,但是这里是我的尝试,只是为了好玩。

读取/排名数据

import pandas as pd

data_source = (
    "https://gist.githubusercontent.com/"
    "notionparallax/7ada7b733216001962dbaa789e246a67/raw/"
    "6d306b5d928b04a5a2395469694acdd8af3cbafb/example.csv"
)

df = (
    pd.read_csv(data_source, index_col=0)
    .assign(ed_name=lambda x: x["ed_name"].str.extract(r"(\d+)").astype(int))
    .value_counts(["ed_name", "source"])
    .groupby("ed_name")
    .rank("first", ascending=False)
    .rename("rank")
    .sort_index()
    .reset_index()
    .query("ed_name < 17")
)

我选择按“first”进行排名,因为这将给我们独占的排名而不是重叠的排名。这使得绘图看起来稍微好看一些,但可能不是您想要的。如果您想要重叠的排名,请使用“min”。

获取上一版中排名前n的内容(用于标记)

n_top_ranked = 10
top_sources = df[df["ed_name"] == df["ed_name"].max()].nsmallest(n_top_ranked, "rank")

简单图形

import matplotlib.pyplot as plt
for i, j in df.groupby("source"):
    plt.plot("ed_name", "rank", "o-", data=j, mfc="w")
plt.ylim(0.5, 0.5 + n_top_ranked)
plt.gca().invert_yaxis()

这里生成的图表不是很好看,但制作起来很简单。

enter image description here

让图表更美观一些

import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FixedFormatter, FixedLocator

fig, ax = plt.subplots(figsize=(8, 5), subplot_kw=dict(ylim=(0.5, 0.5 + n_top_ranked)))

ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))

yax2 = ax.secondary_yaxis("right")
yax2.yaxis.set_major_locator(FixedLocator(top_sources["rank"].to_list()))
yax2.yaxis.set_major_formatter(FixedFormatter(top_sources["source"].to_list()))

for i, j in df.groupby("source"):
    ax.plot("ed_name", "rank", "o-", data=j, mfc="w")

ax.invert_yaxis()
ax.set(xlabel="Edition", ylabel="Rank", title="Popularity of publisher by edition")
ax.grid(axis="x")
plt.tight_layout()

这将使您获得以下内容:enter image description here

还有许多工作要做,以使其看起来真的很好(例如,需要整理颜色),但希望此答案中的某些内容能让您更接近目标。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接