如何在 R 或 Python 中制作旭日形图?

Posted

技术标签:

【中文标题】如何在 R 或 Python 中制作旭日形图?【英文标题】:How to make a sunburst plot in R or Python? 【发布时间】:2012-10-07 06:23:01 【问题描述】:

到目前为止,我一直无法找到可以创建像 John Stasko 那样的旭日形图的 R 库。有谁知道如何在 R 或 Python 中实现这一点?

【问题讨论】:

这些被称为“树图”的矩形等价物非常流行。如果您搜索“圆形树形图”或类似内容,您可能会有更好的运气。 【参考方案1】:

在极坐标投影中使用 matplotlib 条的 Python 版本的旭日形图:

import numpy as np
import matplotlib.pyplot as plt

def sunburst(nodes, total=np.pi * 2, offset=0, level=0, ax=None):
    ax = ax or plt.subplot(111, projection='polar')

    if level == 0 and len(nodes) == 1:
        label, value, subnodes = nodes[0]
        ax.bar([0], [0.5], [np.pi * 2])
        ax.text(0, 0, label, ha='center', va='center')
        sunburst(subnodes, total=value, level=level + 1, ax=ax)
    elif nodes:
        d = np.pi * 2 / total
        labels = []
        widths = []
        local_offset = offset
        for label, value, subnodes in nodes:
            labels.append(label)
            widths.append(value * d)
            sunburst(subnodes, total=total, offset=local_offset,
                     level=level + 1, ax=ax)
            local_offset += value
        values = np.cumsum([offset * d] + widths[:-1])
        heights = [1] * len(nodes)
        bottoms = np.zeros(len(nodes)) + level - 0.5
        rects = ax.bar(values, heights, widths, bottoms, linewidth=1,
                       edgecolor='white', align='edge')
        for rect, label in zip(rects, labels):
            x = rect.get_x() + rect.get_width() / 2
            y = rect.get_y() + rect.get_height() / 2
            rotation = (90 + (360 - np.degrees(x) % 180)) % 360
            ax.text(x, y, label, rotation=rotation, ha='center', va='center') 

    if level == 0:
        ax.set_theta_direction(-1)
        ax.set_theta_zero_location('N')
        ax.set_axis_off()

例如,如何使用这个函数:

data = [
    ('/', 100, [
        ('home', 70, [
            ('Images', 40, []),
            ('Videos', 20, []),
            ('Documents', 5, []),
        ]),
        ('usr', 15, [
            ('src', 6, [
                ('linux-headers', 4, []),
                ('virtualbox', 1, []),

            ]),
            ('lib', 4, []),
            ('share', 2, []),
            ('bin', 1, []),
            ('local', 1, []),
            ('include', 1, []),
        ]),
    ]),
]

sunburst(data)

【讨论】:

这是最优雅的答案!喜欢递归。 处理简单,可扩展,不需要额外的库;纯粹的天才。这值得更多的支持。 bl.ocks.org/mbostock/4348373 这种交互式可缩放可视化可以在 R 中完成吗?【参考方案2】:

您现在甚至可以使用 R 轻松构建交互式版本:

# devtools::install_github("timelyportfolio/sunburstR")

library(sunburstR)
# read in sample visit-sequences.csv data provided in source
# https://gist.github.com/kerryrodden/7090426#file-visit-sequences-csv
sequences <- read.csv(
  system.file("examples/visit-sequences.csv",package="sunburstR")
  ,header=F
  ,stringsAsFactors = FALSE
)

sunburst(sequences)

...当您将鼠标移到上面时,神奇的事情发生了:

编辑 这个包的官方网站可以在这里找到(有很多例子!):https://github.com/timelyportfolio/sunburstR

向创建了这段令人印象深刻的代码的@timelyportfolio 致敬!

【讨论】:

@Dror:这可能会让你感兴趣 :-) 感谢您添加更新。我当然更喜欢交互式版本。任何寻找的人请随时在github.com/timelyportfolio/sunburstR 提供反馈、想法、批评、用例、示例 @timelyportfolio:谢谢,我添加了答案的链接:-) 向下钻取时,有没有办法控制事件的长度和顺序?在您的示例中,从 home-&gt;product-&gt;product-&gt;product-&gt;product-&gt;account 开始的序列中有 6 个事件。我在序列中有 24 个事件。是否可以动态扩展顶部显示的序列,而不会切断任何单词?谢谢。 @kRazzyR:我不是代码的作者。最好将您的问题放在这里:github.com/timelyportfolio/sunburstR/issues【参考方案3】:

您可以使用ggplot2 包中的geom_tile 创建类似于旭日形图的东西。让我们先创建一些随机数据:

require(ggplot2); theme_set(theme_bw())
require(plyr)
dat = data.frame(expand.grid(x = 1:10, y = 1:10),
                 z = sample(LETTERS[1:3], size = 100, replace = TRUE))

然后创建光栅图。这里,图中的x 轴与dat 中的x 变量耦合,y 轴与y 变量耦合,像素填充与z 变量耦合。这会产生以下情节:

p = ggplot(dat, aes(x = x, y = y, fill = z)) + geom_tile() 
print(p)

ggplot2 包支持各种坐标变换,其中一种取一个轴并将其投影到一个圆上,即极坐标:

p + coord_polar()

这大致可以满足您的需求,现在您可以调整dat 以获得所需的结果。

【讨论】:

我承认它有效,但它看起来不像生产质量。我在环之间得到不规则的空白。但是感谢您的努力! 我没有不规则的空白,至少在我上面发布的 png 中没有。你有最新的ggplot2版本吗? 我刚刚更新了它以确保,我仍然拥有它。我使用的是 Windows 机器。 当您将图形转储到文件时,问题可能会消失。 那是极地网格,不是旭日形!【参考方案4】:

有一个名为ggsunburst 的包。遗憾的是不在 CRAN 中,但您可以按照网站上的说明进行安装:http://genome.crg.es/~didac/ggsunburst/ggsunburst.html

希望它对仍在寻找这样一个好包的人有所帮助。

问候,

【讨论】:

【参考方案5】:

这是一个有两层的ggplot2 sunburst。

基本思想是为每一层制作不同的条,并使外层的条更宽。我还弄乱了 x 轴,以确保内部饼图的中间没有孔。因此,您可以通过更改宽度和 x 轴值来控制旭日形的外观。

library(ggplot2)

# make some fake data
df <- data.frame(
    'level1'=c('a', 'a', 'a', 'a', 'b', 'b', 'c', 'c', 'c'), 
    'level2'=c('a1', 'a2', 'a3', 'a4', 'b1', 'b2', 'c1', 'c2', 'c3'), 
    'value'=c(.025, .05, .027, .005, .012, .014, .1, .03, .18))

# sunburst plot
ggplot(df, aes(y=value)) +
    geom_bar(aes(fill=level1, x=0), width=.5, stat='identity') + 
    geom_bar(aes(fill=level2, x=.25), width=.25, stat='identity') + 
    coord_polar(theta='y')

与特定于 sunburst 的软件相比,它的唯一缺点是它假设您希望外层是集体详尽的(即没有间隙)。 “部分穷举”外层(如在其他一些示例中)当然是可能的,但更复杂。

为了完整起见,这里用更好的格式和标签进行了清理:

library(data.table)

# compute cumulative sum for outer labels
df <- data.table(df)
df[, cumulative:=cumsum(value)-(value/2)]

# store labels for inner circle
inner_df <- df[, c('level1', 'value'), with=FALSE]
inner_df[, level1_value:=sum(value), by='level1']
inner_df <- unique(text_df[, c('level1', 'level1_value'), with=FALSE])
inner_df[, cumulative:=cumsum(level1_value)]
inner_df[, prev:=shift(cumulative)]
inner_df[is.na(prev), position:=(level1_value/2)]
inner_df[!is.na(prev), position:=(level1_value/2)+prev]

colors <- c('#6a3d9a', '#1F78B4', '#33A02C', '#3F146D', '#56238D', '#855CB1', '#AD8CD0', '#08619A', '#3F8DC0', '#076302', '#1B8416', '#50B74B')
colorNames <- c(unique(as.character(df$level1)), unique(as.character(df$level2)))
names(colors) <- colorNames

ggplot(df, aes(y=value, x='')) +
    geom_bar(aes(fill=level2, x=.25), width=.25, stat='identity') + 
    geom_bar(aes(fill=level1, x=0), width=.5, stat='identity') + 
    geom_text(data=inner_df, aes(label=level1, x=.05, y=position)) + 
    coord_polar(theta='y') + 
    scale_fill_manual('', values=colors) +
    theme_minimal() + 
    guides(fill=guide_legend(ncol=1)) +
    labs(title='') + 
    scale_x_continuous(breaks=NULL) + 
    scale_y_continuous(breaks=df$cumulative, labels=df$level2, 5) + 
    theme(axis.title.x=element_blank(), axis.title.y=element_blank(), panel.border=element_blank(), panel.grid=element_blank())

【讨论】:

【参考方案6】:

据我所知,只有几个库本身可以做到这一点:

javascript Infovis Toolkit (jit) (example)。 D3.js OCaml 的 Simple Plot Tool (SPT)。

这些都不是在 Python 或 R 中,但是让一个 python/R 脚本写出一个可以由任何一个 javascript 库加载的简单 JSON 文件应该是可以实现的。

【讨论】:

两年后这个答案有更新吗?【参考方案7】:

由于jbkunst提到了ggsunburst,这里我贴一个sirex复制sunburst的例子。

并不完全相同,因为在 ggsunburst 中,节点的角度等于其子节点的角度之和。

# install ggsunburst package
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("rPython")) install.packages("rPython")
install.packages("http://genome.crg.es/~didac/ggsunburst/ggsunburst_0.0.9.tar.gz", repos=NULL, type="source")
library(ggsunburst)

# dataframe
# each row corresponds to a node in the hierarchy
# parent and node are required, the rest are optional attributes
# the attributes correspond to the node, not its parent
df <- read.table(header = T, sep = ",", text = "
parent,node,size,color,dist
,/,,B,1
/,home,,D,1
home,Images, 40,E,1
home,Videos, 20,E,1
home,Documents, 5,E,1
/,usr,,D,1
usr,src,,A,1
src,linux-headers, 4,C,1.5
src,virtualbox, 1,C,1.5
usr,lib, 4,A,1
usr,share, 2,A,1
usr,bin, 1,A,1
usr,local, 1,A,1
usr,include, 1,A,1
")

write.table(df, 'df.csv', sep = ",", row.names = F)

# compute coordinates from dataframe
# "node_attributes" is used to pass the attributes other than "size" and "dist", 
# which are special attributes that alter the dimensions of the nodes
sb <- sunburst_data('df.csv', sep = ",", type = "node_parent", node_attributes = "color")

# plot
sunburst(sb, node_labels = T, node_labels.min = 10, rects.fill.aes = "color") +
  scale_fill_brewer(palette = "Set1", guide = F)

【讨论】:

【参考方案8】:

这是一个使用 R 和 plotly 的示例(基于我的回答 here):

library(datasets)
library(data.table)
library(plotly)

as.sunburstDF <- function(DF, valueCol = NULL)
  require(data.table)
  
  colNamesDF <- names(DF)
  
  if(is.data.table(DF))
    DT <- copy(DF)
   else 
    DT <- data.table(DF, stringsAsFactors = FALSE)
  
  
  DT[, root := names(DF)[1]]
  colNamesDT <- names(DT)
  
  if(is.null(valueCol))
    setcolorder(DT, c("root", colNamesDF))
   else 
    setnames(DT, valueCol, "values", skip_absent=TRUE)
    setcolorder(DT, c("root", setdiff(colNamesDF, valueCol), "values"))
  
  
  hierarchyCols <- setdiff(colNamesDT, "values")
  hierarchyList <- list()
  
  for(i in seq_along(hierarchyCols))
    currentCols <- colNamesDT[1:i]
    if(is.null(valueCol))
      currentDT <- unique(DT[, ..currentCols][, values := .N, by = currentCols], by = currentCols)
     else 
      currentDT <- DT[, lapply(.SD, sum, na.rm = TRUE), by=currentCols, .SDcols = "values"]
    
    setnames(currentDT, length(currentCols), "labels")
    hierarchyList[[i]] <- currentDT
  
  
  hierarchyDT <- rbindlist(hierarchyList, use.names = TRUE, fill = TRUE)
  
  parentCols <- setdiff(names(hierarchyDT), c("labels", "values", valueCol))
  hierarchyDT[, parents := apply(.SD, 1, function(x)fifelse(all(is.na(x)), yes = NA_character_, no = paste(x[!is.na(x)], sep = ":", collapse = " - "))), .SDcols = parentCols]
  hierarchyDT[, ids := apply(.SD, 1, function(x)paste(x[!is.na(x)], collapse = " - ")), .SDcols = c("parents", "labels")]
  hierarchyDT[, c(parentCols) := NULL]
  return(hierarchyDT)


DF <- as.data.table(Titanic)
setcolorder(DF, c("Survived", "Class", "Sex", "Age", "N"))
sunburstDF <- as.sunburstDF(DF, valueCol = "N")

# Sunburst
plot_ly(data = sunburstDF, ids = ~ids, labels= ~labels, parents = ~parents, values= ~values, type='sunburst', branchvalues = 'total')

# Treemap
# plot_ly(data = sunburstDF, ids = ~ids, labels= ~labels, parents = ~parents, values= ~values, type='treemap', branchvalues = 'total')

可以在here找到一些额外的信息。

【讨论】:

以上是关于如何在 R 或 Python 中制作旭日形图?的主要内容,如果未能解决你的问题,请参考以下文章

Plotly 旭日形图周围的值注释

如何实现条形图的日期范围

可缩放旭日形图 (D3.js) 中损坏的图例和工具提示

根据R中的selectinput值更改图表

如何根据python中.groupby函数制作的表格绘制多条形图?

如何利用Python中的Pandas库绘制柱形图