How to make the "connectivity fish" in R

R is a powerful tool for data analysis in the sciences, and I highly recommend any scientist, young and old, dive in and learn to code if they haven't already. But R (and coding) isn't just for science!

We all have interests and hobbies outside of the sciences, and many of mine are in the arts and graphic design. For my new lab webpage, I wanted a nice looking graphic that communicated both my research and my design interests.

Marine organisms are beautiful, as are networks - so I thought, how about turning a marine organism into a patch network? Fish have easily recognizable silhouettes (as opposed to coral), so I thought I'd try a grouper. I started by doing a search for royalty free images of groupers, and then converted a photograph I found into a simplified black and white image. I did this in Illustrator, but it could be done in many different softwares, including R or Matlab.

fish_out.png

Figuring out what lines I wanted to keep was trial and error - many iterations between Illustrator and R. I settled on an image that had a good silhouette, and some internal detail that was satisfyingly "fishy".

The R part

Next, I brought the image (PNG) into R and started playing around with turning a raster into a graph object. I then played with the number of nodes and edges in the graph, their sizes and weights, and how I wanted them to plot. This is purely an art project at this point!

First, I used these packages:

library(png)
library(raster)
library(igraph)

Read in the image:

img <- readPNG("workinprog-01.png")

Extract just the black pixels, and save as a raster:

fish = img[,,1]
fish <- as.raster(fish)

Find and save the indices of the black pixels:

fish.ind = which(fish == "#000000", arr.ind = TRUE)
fish.ind = as.data.frame(fish.ind)

colnames(fish.ind)[2] = "x"
colnames(fish.ind)[1] = "y"

fish.ind$y = -fish.ind$y+max(fish.ind$y) # Flip the image

There were too many pixels for the look I wanted, so I subsampled them:

fish.sub <- fish.ind[sample(length(fish.ind$x),round(length(fish.ind$x)*.05)),]

I created a distance matrix to create edges:

dist.fish <- as.matrix(dist(fish.sub))
dist.fish <- exp(-.00002*dist.fish)
dist.fish <- dist.fish * runif(length(dist.fish),0,2)
dist.fish <- (dist.fish - min(dist.fish))/(max(dist.fish)-min(dist.fish))
diag(dist.fish) <- 0

... and created a "size" for each patch:

A.fish <- runif(length(fish.sub$x),1,6)

... and a graph:

FG <- graph_from_adjacency_matrix(dist.fish, mode = "directed", weighted = TRUE)

Here's the code for plotting the graph with igraph:

col1 <- colorRampPalette(c("grey83", "royalblue4"))(100)
colBFG <- round(((A.fish-min(A.fish))/(max(A.fish)-min(A.fish)))*100)
fish.loc <- data.frame(fish.sub$x,fish.sub$y)
lo <- as.matrix(fish.loc)/10 #Preserve a layout

FG.2 <- FG
FG.3 <- delete.edges(FG.2, sample(E(FG.2),round(.2*length(E(FG.2))))) # Delete some proportion of edges (for looks)

plot(FG.3,
 vertex.color = col1[colBFG],
 vertex.frame.color = "black",
 vertex.label = NA,
 vertex.size = A.fish*20,
 edge.width = sqrt((E(FG.3)$weight-min(E(FG.3)$weight))/(max(E(FG.3)$weight)-min(E(FG.3)$weight)))*.5,
 edge.arrow.size = 0,
 edge.arrow.width = 0,
 edge.curved = rep(-.4,length(E(FG.3))),
 layout = lo,
 rescale = FALSE,
 ylim = c(-10,200),
 xlim = c(-10,550))

The result!

Fish_diff_01-01_1500.png
Daniel Holstein