I created a network using networkx and plotly as following:
edges = df.stack().reset_index()
edges.columns = ['var_1','var_2','correlation']
edges = edges.loc[ (edges['correlation'] < -0.6) | (edges['correlation'] > 0.6) & (edges['var_1'] != edges['var_2']) ].copy()
#create undirected graph with weights corresponding to the correlation magnitude
G0 = nx.from_pandas_edgelist(edges, 'var_1', 'var_2', edge_attr=['correlation'])
mst = G0
# assign colours to edges depending on positive or negative correlation
# assign edge thickness depending on magnitude of correlation
edge_colours = []
edge_width = []
for key, value in nx.get_edge_attributes(mst, 'correlation').items():
edge_colours.append(assign_colour(value))
edge_width.append(assign_thickness(value))
node_size = []
degrees = [val for key, val in dict(G0.degree).items()]
max_deg = max(degrees)
min_deg = min(degrees)
for value in degrees:
node_size.append(assign_node_size(value,min_deg,max_deg))
#draw the network:
nx.draw(mst, pos=nx.fruchterman_reingold_layout(mst),
node_size=15, edge_color=edge_colours, node_colour="black",
edge_width=0.2)
plt.show()
def get_coordinates(G=mst):
"""Returns the positions of nodes and edges in a format for Plotly to draw the network"""
# get list of node positions
pos = nx.fruchterman_reingold_layout(mst)
Xnodes = [pos[n][0] for n in mst.nodes()]
Ynodes = [pos[n][1] for n in mst.nodes()]
Xedges_red = []
Yedges_red = []
Xedges_green = []
Yedges_green = []
def insert_edge(Xedges, Yedges):
Xedges.extend([pos[e[0]][0], pos[e[1]][0], None])
Yedges.extend([pos[e[0]][1], pos[e[1]][1], None])
search_dict = nx.get_edge_attributes(mst, 'correlation')
for e in mst.edges():
correlation = search_dict[(e[0], e[1])]
if correlation <= 0 : # red_edges
insert_edge(Xedges_red, Yedges_red)
else:
insert_edge(Xedges_green, Yedges_green)
# x coordinates of the nodes defining the edge e
return Xnodes, Ynodes, Xedges_red, Yedges_red, Xedges_green, Yedges_green
node_label = list(mst.nodes())
node_label = [fix_string(x) for x in node_label]
# get coordinates for nodes and edges
Xnodes, Ynodes, Xedges_red, Yedges_red, Xedges_green, Yedges_green = get_coordinates()
external_data = [list(x) for x in coding_names_df.values]
external_data = {fix_string(x[0]): x[1] for x in external_data}
external_data2 = [list(y) for y in coding_names_df.values]
external_data2 = {fix_string(y[0]): y[2] for y in external_data2}
external_data3 = [list(z) for z in coding_names_df.values]
external_data3 = {fix_string(z[0]): z[3] for z in external_data3}
external_data4 = [list(s) for s in coding_names_df.values]
external_data4 = {fix_string(s[0]): s[4] for s in external_data4}
# =============================================================================
description = [f"<b>{index}) {node}</b>"
"<br><br>Realm: " +
"<br>" + external_data.get(node, 'No external data found') +
"<br><br>Type: " +
"<br>" + external_data2.get(node, 'No external data found')
for index, node in enumerate(node_label)]
# =============================================================================
# def nodes colours:
node_colour = [assign_node_colour(node, external_data3, coding_names_df) for node in node_label]
node_shape = [assign_node_shape(node, external_data4, coding_names_df) for node in node_label]
# edges
# negative:
tracer_red = go.Scatter(x=Xedges_red, y=Yedges_red,
mode='lines',
line= dict(color="#FA0000", width=1),
hoverinfo='none',
showlegend=False)
# positive:
tracer_green = go.Scatter(x=Xedges_green, y=Yedges_green,
mode='lines',
line= dict(color= "#29A401", width=1),
hoverinfo='none',
showlegend=False)
# nodes
tracer_marker = go.Scatter(x=Xnodes, y=Ynodes,
mode='markers+text',
textposition='top center',
marker=dict(size=node_size,
line=dict(width=0.8, color='black'),
color=node_colour,
symbol=node_shape),
hovertext=description,
hoverinfo='text',
textfont=dict(size=7),
showlegend=False)
axis_style = dict(title='',
titlefont=dict(size=20),
showgrid=False,
zeroline=False,
showline=False,
ticks='',
showticklabels=False)
layout = dict(title='',
width=1300,
height=900,
autosize=False,
showlegend=False,
xaxis=axis_style,
yaxis=axis_style,
hovermode='closest',
plot_bgcolor = '#fff')
fig = dict(data=[tracer_red, tracer_green, tracer_marker], layout=layout)
display(HTML("""<p>Node sizes are proportional to the size of annualised returns.<br>
Node colours signify positive or negative returns since beginning of the timeframe.</p> """))
plot(fig)
and I got this plot: network
I want to add labels, but it's getting too crowded (especially in the middle)
so I have two questions:
How can I spacing the middle? (but still to keep the fruchterman_reingold possition)
How can I add just a few specific labels?
any help will be graet! Tnx :)
Something you could try is setting the k parameter in the layout algorithm, which as mentioned in the docs it sets:
k: (float (default=None)) – Optimal distance between nodes. If None the distance is set to 1/sqrt(n) where n is the number of nodes. Increase this value to move nodes farther apart.
So by playing a bit with this value, and increasing accordingly we can get a more spread out layout and avoid overlap between node labels.
Here's a simple example to illustrate what the behavior is:
result_set = {('plant','tree'), ('tree','oak'), ('flower', 'rose'), ('flower','daisy'), ('plant','flower'), ('tree','pine'), ('plant','roots'), ('animal','fish'),('animal','bird'), ('bird','robin'), ('bird','falcon'), ('animal', 'homo'),('homo','homo-sapiens'), ('animal','reptile'), ('reptile','snake'),('fungi','mushroom'), ('fungi','mold'), ('fungi','toadstool'),('reptile','crocodile'), ('mushroom','Portabello'), ('mushroom','Shiitake'),('pine','roig'),('pine','pinyer'), ('tree','eucaliptus'),('rose','Floribunda'),('rose','grandiflora')}
G=nx.from_edgelist(result_set)
pos=nx.fruchterman_reingold_layout(G)
plt.figure(figsize=(8,5))
nx.draw(G, pos=pos,
with_labels=True,
nodesize=1000,
node_color='lightgreen')
And if we increase the value of k to say 0.5, we get a nice spreading of the nodes in the layout:
pos_spaced=nx.fruchterman_reingold_layout(G, k=0.5, iterations=100)
plt.figure(figsize=(10,6))
nx.draw(G,
pos=pos_spaced,
with_labels=True,
nodesize=1000,
node_color='lightgreen')
How can I add just a few specific labels?
For this you set the labels parameters in draw to a dictionary containing the labels you want displayed. In the case the node names are the same as the labels, just create a dictionary mapping node->node as follows:
show_labels = ['plant', 'tree', 'oak', 'eucaliptus']
pos_spaced=nx.fruchterman_reingold_layout(G, k=0.54, iterations=100)
plt.figure(figsize=(10,6))
nx.draw(G,
pos=pos_spaced,
with_labels=True,
nodesize=1000,
labels=dict(zip(show_labels,show_labels)),
node_color='lightgreen')
I want to visualise conversion of filters. I would like to plot a scatter plot, where every half second the next filter's values are plotted.
My objectives are:
Plot all values up to point (k) but to have values(k) indicated on the plot.
Pause between plotting values for (k) and (k+1)
Plot at full screen
Have the plot after finishing all iteration
I did a function but it is very inefficient and everything slows down after plotting some values.
The only way I found is to use interactive plot ion() and every step plot all points again with updated marker. For each step (k) I would like to rather remove previous points (k-1) and add them in them with different marker and add current points (k)
import pylab as pl
import time
xPos1 = pl.arange(100)
m1 = [pl.sin(pl.pi*x/10) for x in xPos1]
m2 = [pl.cos(pl.pi*x/30) for x in xPos1]
m3 = [pl.sin(pl.pi*x/20) for x in xPos1]
trueVal1 = [0 for real in xPos1]
def conversionAnim(xPos, trueVal, *args):
mTuple = [arg for arg in args]
colorList = ['Green','Blue','Orchid','Cyan','Goldenrod','Salmon','Orange','Violet','Magenta']
f = pl.figure(figsize =(17,8))
pl.ion()
pl.xlim(min(xPos)-1, max(xPos)+1)
pl.ylim(min(j for i in mTuple for j in i)-.5, max(j for i in mTuple for j in i)+.5)
for i in range(len(xPos)):
print '\ni = %i' % i
for j in range(len(mTuple)):
m = mTuple[j]
mVal = [element for element in m]
print 'Value%i is %s' %(j,mVal[i])
if i == 0:
pl.hold(True)
pl.scatter(xPos[i],mVal[i],s=50, marker = 'o', color = 'Dark'+colorList[j])
pl.plot(xPos[i],trueVal[i])
else:
pl.scatter(xPos[i],mVal[i],s=50, marker = 'o',color = 'Dark'+colorList[j])
pl.scatter(xPos[i-1], mVal[i-1],s=50, marker = 'o', color = 'white')
pl.scatter(xPos[i-1], mVal[i-1],s=50, marker = 'x', color = colorList[j])
pl.plot(xPos[i-1:i+1],trueVal[i-1:i+1], color = 'red')
pl.draw()
time.sleep(.01)
time.sleep(3) # to hold figure after its shown
if __name__ == '__main__':
conversionAnim(xPos1, trueVal1, m1, m2, m3)
I don't know how to get around ion() and make this function efficient.
The simplest way to make this more efficent is to use 2N line plots instead of a huge number of scatter plots. (It looks like you end up with three scatter plot for every data point!)
As a side note, you have several lines ( mTuple = [arg for arg in args]) that turn tuples in to lists. It is clearer to write mTuple = list(args), but I don't think you actually need to do those conversions, as you just need an iterable for everything you do.
import itertools
def covnersion_Anim(xPos,trueVal,*args):
mTuple = args
plt_bulk_lst = []
plt_head_lst = []
color_list = ['Green','Blue','Orchid','Cyan','Goldenrod','Salmon','Orange','Violet','Magenta']
f = plt.figure(figsize =(17,8))
ax = plt.gca()
ax.set_xlim([min(xPos),max(xPos)])
ax.set_ylim([0,1])
ms = 5
for j,c in zip(range(len(mTuple)),itertools.cycle(color_list)):
plt_bulk_lst.append(ax.plot([],[],color=c,ms=ms,marker='x',linestyle='none')[0])
plt_head_lst.append(ax.plot([xPos[0]],[mTuple[j][0]],color='Dark'+c,ms=ms,marker='o',linestyle='none')[0])
real_plt, = plot([],[],color='red')
for j in range(1,len(xPos)):
print j
for hd_plt,blk_plt,m in zip(plt_head_lst,plt_bulk_lst,mTuple):
hd_plt.set_xdata([xPos[j]])
hd_plt.set_ydata([m[j]])
blk_plt.set_ydata(m[:j])
blk_plt.set_xdata(xPos[:j])
real_plt.set_xdata(xPos[:j])
real_plt.set_ydata(trueVal[:j])
plt.pause(1)
return f
covnersion_Anim(range(12),rand(12),rand(12),rand(12),rand(12))
So far I have been unable to find an R library that can create a sunburst plot like those by John Stasko. Anyone knows how to accomplish that in R or Python?
Python version of sunburst diagram using matplotlib bars in polar projection:
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()
Example, how this function can be used:
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)
You can even build an interactive version quite easily with R now:
# 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)
...and when you move your mouse above it, the magic happens:
Edit
The official site of this package can be found here (with many examples!): https://github.com/timelyportfolio/sunburstR
Hat Tip to #timelyportfolio who created this impressive piece of code!
You can create something along the lines of a sunburst plot using geom_tile from the ggplot2 package. Let's first create some random data:
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))
And then create the raster plot. Here, the x axis in the plot is coupled to the x variable in dat, the y axis to the y variable, and the fill of the pixels to the z variable. This yields the following plot:
p = ggplot(dat, aes(x = x, y = y, fill = z)) + geom_tile()
print(p)
The ggplot2 package supports all kinds of coordinate transformations, one of which takes one axis and projects it on a circle, i.e. polar coordinates:
p + coord_polar()
This roughly does what you need, now you can tweak dat to get the desired result.
Theres a package called ggsunburst. Sadly is not in CRAN but you can install following the instruction in the website: http://genome.crg.es/~didac/ggsunburst/ggsunburst.html.
Hope it helps to people who still looking for a good package like this.
Regards,
Here's a ggplot2 sunburst with two layers.
The basic idea is to just make a different bar for each layer, and make the bars wider for the outer layers. I also messed with the x-axis to make sure there's no hole in the middle of the inner pie chart. You can thus control the look of the sunburst by changing the width and x-axis values.
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')
The only disadvantage this has compared to sunburst-specific software is that it assumes you want the outer layers to be collectively exhaustive (i.e. no gaps). "Partially exhaustive" outer layers (like in some of the other examples) are surely possible but more complicated.
For completeness, here it is cleaned up with nicer formatting and labels:
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())
There are only a couple of libraries that I know of that do this natively:
The Javascript Infovis Toolkit (jit) (example).
D3.js
OCaml's Simple Plot Tool (SPT).
Neither of these are in Python or R, but getting a python/R script to write out a simple JSON file that can be loaded by either of the javascript libraries should be pretty achievable.
Since jbkunst mentioned ggsunburst, here I post an example for reproducing the sunburst by sirex.
It is not exactly the same because in ggsunburst the angle of a node is equal to the sum of the angles of its children nodes.
# 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)
Here is an example using R and plotly (based on my answer 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')
Some additional information can be found here.
You can also use plotly Sunburst on python as well as seen here
The same inputs can be used to create Icicle and Treemap graphs (supported too by plotly) which might also suit your needs.