R: Modifying Graphs - r

I posted a comment/reply to another stackoverflow post over here : R: Understanding Graph relating to graphs in R.
If you create some data corresponding to movies and actors (in which movies can not be connected to other movies directly, and actors can not be connected to other actors directly), you write some R code to check if your graph is bipartite:
library(igraph)
film_data <- data.frame(
"movie" = c("movie_1", "movie_1", "movie_1", "movie_2", "movie_2", "movie_2", "movie_3", "movie_3", "movie_3", "movie_4", "movie_4", "movie_4", "movie_4", "movie_5", "movie_5", "movie_5", "movie_6", "movie_6"),
"actor" = c("actor_1", "actor_2", "actor_3", "actor_2", "actor_3", "actor_4", "actor_1", "actor_5", "actor_6", "actor_2", "actor_7", "actor_1", "actor_8", "actor_5", "actor_9", "actor_3", "actor_2", "actor_8")
)
#create directed graph
graph <- graph.data.frame(film_data, directed=F)
graph <- simplify(graph)
plot(graph)
V(graph)$type <- V(graph)$name %in% film_data[,1]
is.bipartite(graph)
[1] TRUE
However, you can "purposefully sabotage" this graph by adding a link between two actors (actor_2 and actor_3) so that the graph is no longer bipartite:
film_data <- data.frame(
"movie" = c("movie_1", "movie_1", "movie_1", "movie_2", "movie_2", "movie_2", "movie_3", "movie_3", "movie_3", "movie_4", "movie_4", "movie_4", "movie_4", "movie_5", "movie_5", "movie_5", "movie_6", "movie_6", "actor_2"),
"actor" = c("actor_1", "actor_2", "actor_3", "actor_2", "actor_3", "actor_4", "actor_1", "actor_5", "actor_6", "actor_2", "actor_7", "actor_1", "actor_8", "actor_5", "actor_9", "actor_3", "actor_2", "actor_8", "actor_3")
)
#create directed graph
graph <- graph.data.frame(film_data, directed=F)
graph <- simplify(graph)
plot(graph)
But R will still say that this graph is bipartite:
V(graph)$type <- V(graph)$name %in% film_data[,1]
is.bipartite(graph)
[1] TRUE
You can further sabotage this graph by adding an extra link between two movies:
film_data <- data.frame(
"movie" = c("movie_1", "movie_1", "movie_1", "movie_2", "movie_2", "movie_2", "movie_3", "movie_3", "movie_3", "movie_4", "movie_4", "movie_4", "movie_4", "movie_5", "movie_5", "movie_5", "movie_6", "movie_6", "actor_2", "movie_1"),
"actor" = c("actor_1", "actor_2", "actor_3", "actor_2", "actor_3", "actor_4", "actor_1", "actor_5", "actor_6", "actor_2", "actor_7", "actor_1", "actor_8", "actor_5", "actor_9", "actor_3", "actor_2", "actor_8", "actor_3", "movie_2")
)
#create directed graph
graph <- graph.data.frame(film_data, directed=F)
graph <- simplify(graph)
plot(graph)
But R will still call it bipartite:
V(graph)$type <- V(graph)$name %in% film_data[,1]
is.bipartite(graph)
[1] TRUE
Does anyone know if I am doing something wrong? Are these last two graphs actually bipartite? Or am I applying the code incorrectly?
Just to clarify: Are all undirected graphs cyclic? If you have a undirected graph with just one type of node, it it necessarily bipartite?
Thanks

Indeed, the graph you created is not bipartite: the part 'actors' has adjacent vertices.
The function is.bipartite() (or its name) is highly misleading. It only tells you if the graph has the required vertex attribute called type. It doesn't check the other characteristics of what makes a graph bipartite. Source: ?is.bipartite

Related

Generate random points on osmnx graph

I have created a road network graph using osmnx library. now I want to generate some random points on the network but I don't have any idea how to do it. need some help :(
here is my code:
import geopandas as gpd
import osmnx as ox
top= gpd.GeoDataFrame(columns = ['name', 'geometry'], crs = 4326, geometry = 'geometry')
top.at[0, 'geometry'] = Point(100.40823730180041,14.207021554191956)
top.at[0, 'name'] = 'tl'
top.at[1, 'geometry'] = Point(100.74774714891429, 14.196946042603166)
top.at[1, 'name'] = 'tr'
bottom= gpd.GeoDataFrame(columns = ['name', 'geometry'], crs = 4326, geometry = 'geometry')
bottom.at[0, 'geometry'] = Point(100.38860578002853,13.612931284522707)
bottom.at[0, 'name'] = 'bl'
bottom.at[1, 'geometry'] = Point(100.7131032869639, 13.581503263247015)
bottom.at[1, 'name'] = 'br'
combined = top.append(bottom)
convex = combined.unary_union.convex_hull
graph_extent = convex.buffer(0.02)
graph = ox.graph_from_polygon(graph_extent, network_type = "drive")
Following are the steps of what I did:
I created two geodataframes top and bottom top define the extent of my road network
Then I combined them and used ox.graph_from_polygon to create a road network.
My road network looks something like this
roadNetwork
Now I want to generate some random points that should be on the links/edges of the network created.
The sample_points function does exactly that. See the OSMnx usage examples and documentation for usage: https://osmnx.readthedocs.io/en/stable/osmnx.html#osmnx.utils_geo.sample_points

Add round feedback arrow to horizontal graph in Graphviz / DiagrammR

I like to add a feedback arrow to a Graphviz graph, where the ordinary "flow" remains horizontal, but the feedback should be round, like the manually added blue arrow below.
Here is what I tried so far. I use the DiagrammR package for the R language but a suggestion for plain or python Graphviz or would of course also be helpful.
library("DiagrammeR")
grViz("digraph feedback {
graph [rankdir = 'LR']
node [shape = box]
Population
node [shape = circle]
Source Sink
node [shape = none]
Source -> Growth -> Population -> Death -> Sink
Population -> Growth [constraint = false]
Death -> Population [constraint = false]
}")
You can try using the headport and tailport options and indicate "north" for both of these (for Population and Growth).
The headport is the cardinal direction for where the arrowhead meets the node.
The tailport is the cardinal direction for where the tail is emitted from the node.
library("DiagrammeR")
grViz("digraph feedback {
graph [rankdir = 'LR']
node [shape = box]
Population
node [shape = circle]
Source Sink
node [shape = none]
Source -> Growth -> Population -> Death -> Sink
Population -> Growth [tailport = 'n', headport = 'n', constraint = false]
}")
Output

Network graph looks different in PNG and PDF using ggplot

I have a weighted undirect graph as an igraph object :
IGRAPH 7d6665b UNW- 168 2345 --
+ attr: name (v/c), label (v/c), degree_alpha (v/n)
+ edges from 7d6665b (vertex names):
[1] 7 --13 7 --15 13--15 11--16 15--17 6 --18 15--20 6 --25 18--25 6 --28 10--28 15--28 18--28 20--28 25--28 23--30 15--31
[18] 17--31 28--31 6 --33 17--33 18--33 25--33 28--33 7 --34 13--34 15--34 16--35 15--37 18--37 20--37 25--37 28--37 13--43
+ ... omitted several edges
I use the function ggnet2 (part of GGally) to plot my igraph object as a ggplot2 object :
ggnet2(graph,
node.size = V(graph)$degree_alpha,
edge.label = NULL,
edge.size = E(graph)$weight/3,
node.label = NULL,
color = "#038e9a",
alpha = 0.75,
edge.color = "#6E6E6E",
legend.size = 0) + guides(color = FALSE, size = FALSE)
When I export to PNG, or just the preview given by RStudio, it looks like this :
When I export to PDF, I have a different output, with thicker edges :
You can find the graph object in RDS format here. How can I make my PDF export looks the same as the PNG one ? What does explain the difference in the first place ?

Inserting hyperlinks into node labels in DiagrammeR

I would like to be able to create flowcharts with DiagrammeR in R so that I can export SVG through the devtools::install_github('rich-iannone/DiagrammeRsvg') package.
My flowcharts must include hyperlinks in some of the nodes, unfortunately I can't find an acceptable way to create node labels with functioning <a> tags. Here are the different methods I've tried:
Mermaid
Using DiagrammeR(diagram = "", type = "mermaid") it's possible to use HTML tags in the node labels:
library("DiagrammeR")
DiagrammeR("graph TB;
A{Is your data public?} -- yes -->C;
A -- no -->B[<center><b>Oxshef: dataviz</b> only supports researchers <br> in the creation of interactive data visualisations that public</center>];
C{<center>Please make it public?</center>};
D[<center>Supported!</center>];
E[<center>Unsupported!</center>];
F[Refer to our tutorial];
C -- yes -->D;
C -- no -->E;
C -- I don't know -->F")
But to use the <a> tag we need to use an = which the parser vomits over:
DiagrammeR("graph TB;
A{Is your data public?} -- yes -->C;
A -- no -->B[<center><b>Oxshef: dataviz</b> only supports researchers <br> in the creation of interactive data visualisations that public</center>];
C{<center>Please make it public?</center>};
D[<center>Supported!</center>];
E[<center>Unsupported!</center>];
F[Refer to our <a href='http://google.com'>tutorial</a>];
C -- yes -->D;
C -- no -->E;
C -- I don't know -->F")
grViz
Here's the same flowchart as above but with all html stripped out and converted to grViz:
grViz("
digraph boxes_and_circles {
# a 'graph' statement
graph [overlap = true, fontsize = 10]
# several 'node' statements
node [shape = box,
fontname = Helvetica]
A [label = 'Is your data public?']; B [label = 'Please make it public'];
C [label = 'Tech Question']; D [label = 'Supported' ];
E [label = 'Unsupported!']; F [label = 'Refer to our tutorial']
# several 'edge' statements
A->B A->C C->D [label = 'yes'] C->E [label = 'no'] C->F [label = 'Unknown']
}
")
This doesn't support HTML tags:
grViz("
digraph boxes_and_circles {
# a 'graph' statement
graph [overlap = true, fontsize = 10]
# several 'node' statements
node [shape = box,
fontname = Helvetica]
A [label = 'Is your data public?']; B [label = '<b>Please</b> make it public'];
C [label = 'Tech Question']; D [label = 'Supported' ];
E [label = 'Unsupported!']; F [label = 'Refer to our tutorial']
# several 'edge' statements
A->B A->C C->D [label = 'yes'] C->E [label = 'no'] C->F [label = 'Unknown']
}
")
create_graph
DiagrammeR also lets us create graph as follows:
ndf_no_tags <- create_node_df(n = 6,
label = c("Is your data public?",
"Please make it public",
"Tech Question",
"Supported",
"Unsupported",
"Refer to our tutorial"))
# Create an edge data frame (edf)
edf <- create_edge_df(from = c(1, 1, 3, 3, 3),
to = c(2, 3, 4, 5, 6))
ndf_no_tags %>%
create_graph(edges_df = edf) %>%
render_graph()
But it escapes HTML tags:
ndf_with_tags <- create_node_df(n = 6,
label = c("Is your data public?",
"<b>Please</b> make it public",
"Tech Question",
"Supported",
"Unsupported",
"Refer to our tutorial"))
ndf_with_tags %>%
create_graph(edges_df = edf) %>%
render_graph()
I figured it out for the mermaid function:
mermaid('
graph LR
A-->B
A-->C
C-->E
B-->D
C-->D
D-->F
E-->F
click B "http://www.github.com" "This is a link"
')
the click B <link> option requires double quotes, and thankfully R accepts single quotes for the entire mermaid code block.

Using NetworkX to Study the Shift Operator and other Mathematical Creations

A branch of operator theory studies the shift operator S. Basically, given a graph with weights assigned to each vertex of the graph, the shift operator produces a new graph by taking the same graph (A) and replacing the weight of each vertex with the sum of the weights of the vertex's neighbors. For example, 3 in graph (A) is replaced by 5 + 5 + 2 + 0.
A
B
Does anyone know if networkx can help me automate such a process for an arbitrary graph, G? Also, what are the limits in size (vertexes, edges, etc) of graphs that I may construct?
First you need to create a graph and add the node weights.
I name the nodes with letters from a to h.
For larger graphs you'll need a different way of naming nodes (so each node has a unique name).
In the code bellow I also draw the node names.
Note that I manually set the node positions so I have the same example as you.
For larger graphs check out graph layouts.
import networkx as nx
from matplotlib import pyplot as plt
G = nx.Graph()
nodes = [
['a', {'weight' : 5}],
['b', {'weight' : 4}],
['c', {'weight' : 2}],
['d', {'weight' : 3}],
['e', {'weight' : 5}],
['f', {'weight' : 0}],
['g', {'weight' : 0}],
['h', {'weight' : 1}]
]
for node in nodes:
G.add_node(node[0], node[1]) # add node and node weight from list
G.add_edges_from([
('a', 'd'),
('b', 'e'),
('c', 'd'),
('d', 'e'),
('d', 'g'),
('e', 'h'),
('e', 'f')
])
pos = {'a' : (1, 2), 'b' : (2, 2), 'c' : (0, 1), 'd' : (1, 1), 'e' : (2, 1), 'f' : (3, 1), 'g' : (1, 0), 'h' : (2, 0)} # manual fixed positions
plt.figure()
nx.draw(G, pos=pos, with_labels=True, node_size=700, node_color='w') # draw node names
plt.show()
Output:
Here is the code which draws the node weights:
plt.figure()
nx.draw(G, pos=pos, labels=nx.get_node_attributes(G, 'weight'), node_size=700, node_color='w') # draw node weights
plt.show()
And finally the code for calculating your shift operator S.
You can get the neighbors of some node node with G[node].
The weight attribute for some node neighbor can be accessed with G.node[neighbor]['weight'].
Using that and list comprehension I sum the list of weights for all neighbor nodes of the current node. Note that the new weights are set with nx.set_node_attributes(G, 'weight', new_weights).
new_weights = {}
for node in G.nodes():
new_weights[node] = sum([G.node[neighbor]['weight'] for neighbor in G[node]]) # sum weights of all neighbors of current node
nx.set_node_attributes(G, 'weight', new_weights) # set new weights
plt.figure()
nx.draw(G, pos=pos, labels=nx.get_node_attributes(G, 'weight'), node_size=700, node_color='w') # draw new node weights
plt.show()
Final graph:

Resources