Visualizing Pi

We celebrate Pi-day (3/14) every year at the library. And though it is december, and there is months to the big day, it is not too early to begin planning. This is something we do in the incredibly amounts of time we have left over when all the other tasks that also only take a tiny portion of the day are done. In other words, this is something we plan in our weekly lunchbreak.

Usually Henrik delivers a small talk about Pi. We eat some Pie, and there is much rejoicing.

But we would like to show something else. A cool visualization perhaps. There are several out there, but it is no fun just to show cool stuff. Its much cooler to understand how it is done.

One of the visualizaions we found on the net is this. https://www.visualcinnamon.com/portfolio/the-art-in-pi It is made by the extremely talented Nadieh Bremer. Take a look at her page. She does a lot of other cool stuff!

So. How to replicate this?

The idea is to take pi. Draw a line segment from 0,0 in the plane to a new point, determined by the first digit. Then we draw another segment from that point, to a newer point, determined by the second digit in Pi. Continue ad nauseam, or until your computer catches fire. Add colours, and make it look cool.

First item is to get pi to a million decimal places. Read it in, and make a vector containing all the digits. I’ll begin with a smaller string.

testnumvec <- "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"

Not quite pi. But I can’t be bothered figuring out how many digits of pi I should use in order to get all the digits from 0 to 9. I want that to make sure that whatever I do, works on all the digits.

Next up is getting rid of the period.

numvec <- gsub("[[:punct:]]", "", testnumvec)

In Denmark we would use a “,” instead of “.” This removes all punctuation.

Now we convert it to a vector with individual digits. And cast them as integers rather than characters.

numvec <- as.integer(unlist(strsplit(numvec,"")))

We begin a (0,0). The next point is determined by the first digit i pi – 3. The number 3 should determine witch direction, angle, we should move. The new x-value will be cos(f(3)), where f(3) is some function, that converts 3 to the correct angle. That should be some fraction of pi. The new y-value is similarly determined by sin(f(3)).

What should f(x) be? Imagine a decimal clock, with the ten digits 0 to 9 positioned around the circle. In the original 0 is at top, at 12 o’clock. 5 is at 6 o’clock. And the numbers follow the circle clockwise. In the unity circle 0 is positioned at 1/2pi. 5 at 1 1/2 pi. So f(0)=1/2pi, f(5)=1 1/2pi.

This formula gives us the result we need: 5/2pi – z/5pi.

z is the digit in pi. For all of them, we can calculate the steps we need in this way:

x <- cos(5/2*pi - numvec/5*pi)
y <- sin(5/2*pi - numvec/5*pi)

So. Beginning at (x0,y0), we calculate the trig functions, add it to (x0,y0) and get (x1,y1). When we continue to do that, we notice, that xi is the cumulative sum of all the calculated x-values. If we calculate the trig functions for all of the digits in pi, and then calculate the cumulative sum, we get all the x-values. Similarly with all the y-values.

Thats neat, and thanks to Henrik, who pointed this out. We get the cumulative sums easily: And we can then get the cumulative sums with:

x <- cumsum(x)
y <- cumsum(y)

Thats it! There is just a little detail. The first value in x,y is the first step we need to take – or the second point if you will. We will need to add a 0 at the start:

x <- c(0,x)
y <- c(0,y)

Thats it! I should now be able to plot it. Lets just do a quick test:

plot(x,y, type="l")

That looks almost like what we need. The most notable difference is that Nadieh only plots the decimals. I include the “3” as well. We are going to test this, and it is cumbersome to do all the preceding steps. Let’s begin with defining the generation of points as a function, that takes a string. In just a little while I’ll invoke the magick of ggplot. So it would be convenient that the result is a dataframe:

piPoints <- function(piString){
  numbers <- gsub("[[:punct:]]", "", piString)
  numbers <- as.integer(unlist(strsplit(numbers,"")))
  x <- cos(5/2*pi - numbers/5*pi)
  y <- sin(5/2*pi - numbers/5*pi)
  x <- cumsum(x)
  y <- cumsum(y)
  x <- c(0,x)
  y <- c(0,y)
  df <- data.frame(as.integer(c(0,numbers)),x,y)
  colnames(df) <- c("num", "x", "y")
  return(df)
}

All right. We now have a dataframe with three columns. The number, and the x,y coordinates. We can plot that with ggplot:

df <- piPoints(testnumvec)
library(ggplot2)

ggplot(df, aes(x=x,y=y,group="1"))+
  geom_path(aes(colour=factor(df$num)))

Weird. Something is wrong. Look at the second linesegment. It goes up and to the right, just as it should, since this segment represents a “1”. But the color? It is green. It should be sorta orangy. The reason is that ggplot looks at the number, and by implication the color, at the origin of the linesegment. Not at the end. How to change that? What I really need to do, is shifting the number column one place relative to the x,y pairs. Simply done – remove the 0 at the beginning of the num-vector in the function. And the final digits in the x and y vectors. I really don’t like it, as it increases the size of the dataframe – but I add an id-column as well. Just a sequential number, to control the colouring:

piPoints <- function(piString){
  numbers <- gsub("[[:punct:]]", "", piString)
  numbers <- as.integer(unlist(strsplit(numbers,"")))
  x <- cos(5/2*pi - numbers/5*pi)
  y <- sin(5/2*pi - numbers/5*pi)
  x <- cumsum(x)
  y <- cumsum(y)
  x <- c(0,x)
  y <- c(0,y)
  id <- 1:(length(y)-1)
  df <- data.frame(as.integer(c(numbers)),x[-length(x)],y[-length(y)], id)
  colnames(df) <- c("num", "x", "y", "id")
  return(df)
}

Lets take another look:

df <- piPoints(testnumvec)
library(ggplot2)

ggplot(df, aes(x=x,y=y,group="1"))+
  geom_path(aes(colour=factor(df$num)))

Now we’re there. Two steps are missing: Adding more digits – I would like to get to one million digits. And getting it to look nice.

Lets try getting to the million. I don’t need to calculate pi to a million decimals. I can just download it.

library(readr)
storpi <- read_file("pi_dec_1m.txt")

df <- piPoints(storpi)
library(ggplot2)

ggplot(df, aes(x=x,y=y,group="1"))+
geom_path(aes(colour=df$id)) +
  scale_colour_gradient(low="red", high="green")

Lets beautify it at bit. theme_bw removes the horrible grey background. coord_fixed(ratio=1) controls the aspect-ratio of the plot. Not really necessary, but in just a short while I’ll expand to other numbers, and would like to compare stuff. And in theme() a lot of stuff is set to blank – we need a clean plot. Finally – or not quite, I change the colours. http://colorbrewer2.org/ helps with choosing colours. Its targeted at maps. But this is a kind of map. I am, more or less working with continous data. So instead of scale_colour_brewer, I use scale_colour_distiller. It’s not quite kosher to use a qualitative scale for this kind of data. But I like the colours, and I really need help choosing them, since I am not a talented designer.

ggplot(df, aes(x=x,y=y,group="1"))+
geom_path(aes(colour=df$id)) +
  scale_colour_distiller(type="qual", palette="Set1") +
    theme_bw() + 
  coord_fixed(ratio = 1) +
  theme(line = element_blank(),
        text = element_blank(),
        title = element_blank(),
        legend.position="none",
        panel.border = element_blank(),
        panel.background = element_blank())
## Warning: Using a discrete colour palette in a continuous scale.
##   Consider using type = "seq" or type = "div" instead

Done!

Whats next?

So. I’ve done pi to a million decimal places. What about other fundamental mathematical constants?. Some of these perhaps? * Square root of 2 (Sqrt(2)) * Square root of 3 (Sqrt(3)) * Golden ratio * e * Natural logarithm of 2 (Log(2)) * Natural logarithm of 3 (Log(3)) * Natural logarithm of 10 (Log(10)) * Apéry’s constant (Zeta(3)) * Lemniscate constant (Lemniscate) * Catalan’s constant (Catalan) * Euler-Mascheroni constant (Euler’s Constant)

And why only a million places? Why not go to a billon?

I feel the need. The need for speed

This is the writeup. So I did all this, without looking at the original code. In the original, the cumulative sums are calculated differently:

x <- y <- rep(NULL, length(numvec))
x[1] <- 0
y[1] <- 0

for (i in 2:length(piVec)){
    x[i] <- x[(i-1)] + sin((pi*2)*(numvec[i]/10))
    y[i] <- y[(i-1)] + cos((pi*2)*(numvec[i]/10))  
}

That is nice. It was also the way I tried to do it. I got lost in apply-functions using lag(). That was definitely not the way to do it.

But is my way faster? I would guess it is, as for-loops in general are not very fast in R. Guessing is not good enough, I need hard data. The way to get it is by measuring the time used. There are several tools for that, and I’ll use microbenchmark, since it provides a neat plot.

You will probably need to install it:

devtools::install_github("olafmersmann/microbenchmarkCore")
devtools::install_github("olafmersmann/microbenchmark")

Lets run the test.

library(microbenchmark)
## Loading required package: microbenchmarkCore
testnumvec <- read_file("pi_dec_1m.txt")
numbers <- gsub("[[:punct:]]", "", testnumvec)
  numbers <- as.integer(unlist(strsplit(numbers,"")))  
numvec <- numbers
mbm <- microbenchmark({
x <- cos(5/2*pi - numvec/5*pi)
y <- sin(5/2*pi - numvec/5*pi)
x <- cumsum(x)
y <- cumsum(y)
x <- c(0,x)
y <- c(0,y)
},{
x <- y <- rep(NULL, length(numvec))
x[1] <- 0
y[1] <- 0
for (i in 2:length(numvec)){
    x[i] <- x[(i-1)] + sin((pi*2)*(numvec[i]/10))
    y[i] <- y[(i-1)] + cos((pi*2)*(numvec[i]/10))  
}
}, times=3

)
levels(mbm$expr) <- c("My way", "Original way")
mbm
## Unit: milliseconds
##          expr       min        lq      mean   median        uq       max
##        My way  177.0869  182.5159  189.8857  187.945  196.2851  204.6253
##  Original way 1677.8690 1721.3408 1787.1957 1764.813 1841.8591 1918.9056
##  neval cld
##      3  a 
##      3   b
autoplot(mbm)

Thats kinda faster. Not that it makes that much of a difference – “My” method does the one million digits in 0.2 seconds. The original takes a little over 9 times as long. Who cares? You are only going to do it once. 2 seconds is not a problem. I would however like to make the same plot with a billion digits. I’m not sure I’ll even be able to plot it. But a naive guess implies that 1.000 times the number of digits will take 200 seconds, or a little more than 3 minutes to run through. 9 times that is almost half an hour. That is a difference you will notice.

Cute Animals – the initial thoughts.

The internet is really a cat-picture delivery system. One thing I have observed on twitter, is that images of cute kittens, in a library setting, receives a LOT of attention. Retweets, likes, all those endorphin inducing things.

Images of cute puppies give the same results. But I have the distinct impression, that people prefer cute kittens over puppies. That, however, is anecdotal evidence. Is there a way to measure it?

What we are interested in is the number of impressions. That is available from analytics.twitter.com. But-but-but. If the puppy-tweet is from november 1st and the kitten-tweet is from december 1st. And I am collecting the impressions on december 2nd, that wont give me a fair comparison. The puppy will have had far longer to collect impressions than the kitten. What I need, is to follow the interactions with the two tweets over time.

A problem with this is, that the twitter API does not give me access to those numbers. I will need another way to get them. I have written a Python script, that collect the csv-files from Twitter analytics for a number of months back. It can be found on my github page. Basically I use the Selenium package to let Python control a browser, that automagically download the statistics. If I do that once every hour, I will be able to graph the number of interactions over time.

I would like a rather comprehensive set of data. Not only kitties and puppies, but also cute hedgehodges, kangaroos etc. It would also be interesting to see if the time the tweet is made makes a difference. And just a single tweet of a cat is not a lot, there should probably be several tweets with each species. We are talking quite a lot of tweets. And quite a lot of data.

I am going to use a google spreadsheet as the backend. That will make it possible to graph the results as they come in.

One thing to consider working with Google Spreadsheets, is the inherent limitations. A workbook has an upper limit of 2.000.000 cells. I am going to gather rather a lot of data.

A quick back-of-envelope calculation:

Once every hour, I am going to download statistics for five months. I probably wont tweet more than 150 times pr month. And I am going to do this for three months, with an average of 30 days in each month. And 24 hours in every day. And for each tweet, I am going to collect 14 variables.

That results in: 14*24*30*3*150*5 = 22.680.000

A bit to much. How to reduce that? First of all, not every tweet I make will be relevant. I am still going to tweet about the benefits of reading instructions before deciding that you can’t figure out how to do something. If I only collect the tweets relevant to this study, it will greatly reduce the amount of data. In the above calculation I collect information on a total of 450 tweets. I will probably only need data on 30 tweets pr month. What also helps, is that the first day, I will get 24 data points on one tweet. On day two, I will get 24 datapoints on two tweets etc. That will probably save me.

The steps will be as follows:

  • Locate a suitable collection of images of cute animals in a library setting.
  • Plan when to tweet them – take into consideration the time of the tweets.
  • Collect – regularly – the data as pr the scripts mentioned above
  • Analyse the data.

 

Tid til en ny nedtælling

Der har været nogen optællinger. Nu tæller vi ned. Der sker noget glædeligt. Noget er slut. Sådan ca. pr. midnat nytårsaften. Det sker noget andet. Det er ikke sikkert at det er bedre. Men dog er det en glædelig begivenhed.

weeks
-44
-6
days
0
-4
hours
0
0
minutes
-2
-7
seconds
-4
-4

Stress. Depression. Help.

Just a simple point:

If you get stressed to the point of spending time in the restroom crying, get help. Please. And do it now. Not a year after your call for help was ignored. Now. Right now.

Severe stress can lead to depressions. Stress in itself can be crippling. Depression is even worse.

Learn to recognize the signs. Not just in yourself. Also in your colleagues. If you are a manager it might be a good idea to learn about them as well.

And remember, it is not your fault that you get a stress induced depression. You may be expected to take the blame for being stressed. You may be expected to keep soldiering on until you break.

But no one is ever going to thank you for it.

Ever.

Those civil war monuments in the US

I once saw a video suggesting that the US should choose Canada as their next president. They would solve the race-problem. As soon as they figured out why there still was a race-problem. It’s one of those things that are pretty hard to understand for europeans. And apparently for canadians as well.

Anyway, there is a problem, and lately it’s been manifesting in protests against monuments celebrating confederate war-“heroes”. Thats another thing that is difficult to understand from across the atlantic. Those monuments must certainly have been standing there for a very long time. After all, the war ended in 1865. They may be celebrating the losers, but seriously, what’s the problem? Well… Actually there is a problem. Most of the monuments were not erected to commemorate fallen soldiers just after the war. They were erected to show those n******, who was still in charge when they started to organize for rights. And again when those rights were granted (to some degree). There’s a lot more information in this report. And suddenly even europeans begin to understand what all the fuss is about. But still. Seriously? Get over it, you lost, be nice, and treat people like, you know, Jesus said you should.

Okay – that was the introduction. And now for the data. Somewhere in there, there is an interesting datavisualization. The data is here:

https://data.world/datadanlarson/confederatemonument/workspace/file?filename=CivilWarMamorials.csv

What do I want to do with it? I want an animated GIF. Each frame in that gif should represent a year, and show a map of the US, with all the monuments erected up to and including that year. Preferably it should be noticable what monuments where erected that year. A graph showing the development of the number of monuments would be nice as well.

Okay, lets get coding. The data comes from data.world, and they have their own R-package:

  devtools::install_github("datadotworld/data.world-r", build_vignettes = TRUE, force = TRUE)

In order to get to the data, You will need a token. There is excellent help to get at data.world.

data.world::set_config(data.world::save_config(auth_token = "redacted"))

It is a horrible token. Anyway, lets plod on. Load their library, and get the data

library(data.world)
dataset_key <- "https://data.world/datadanlarson/confederatemonument"
tables_qry <- data.world::qry_sql("SELECT * FROM Tables")
tables_df <- data.world::query(tables_qry, dataset = dataset_key)
sample_qry <- data.world::qry_sql(sprintf("SELECT * FROM `%s`", tables_df$tableName[[1]]))
sample_df <- data.world::query(sample_qry, dataset = dataset_key)

This is a simple copy-paste from the examples at data.world. The net result is, that we have all the data in the dataframe sample_df.

We are interested in the year a monument was erected. Not all years are known. And this is of course a serious flaw in the following visualization. Patterns may not be what they appear, when approx. half the data is missing.

Anyway, lets get rid of the rows with missing data:

newdata <- sample_df[complete.cases(sample_df),]

complete.cases returns a logical vector. True if the row is free from NAs, eg. is complete, False if there is an NA in it. newdata is now a dataframe with only the complete cases.

We need coordinates. Google is our friend, however, I’m not going to run that geocoding again. Google allows 2500 calls to their API every day from a given IP-number. It is easy to run out of calls.

library(ggmap)
newerdata <- data.frame(city=character(), state = character(), year = numeric(), status = character(), lat = numeric(), lon = numeric(), stringsAsFactors = FALSE)
for (row in 1:nrow(newdata)){
result <- geocode(paste(newdata[row,2]$city, newdata[row,1]$state, sep=", "), output="latlon", source="google")

    newerdata[nrow(newerdata) + 1,] = c(newdata[row,2]$city,newdata[row,1]$state, as.numeric(newdata[row,5]$year), newdata[row,6]$civilwarstatus, result$lat, result$lon)
  
}
save(newerdata, file="newestdata.rda")

What happens? We call ggmap, which provides the function geocode. A new dataframe, newerdata, is defined. For each row in newdata, I call geogode on the city and state, separated with a “,”, and saves the result in “result”. And then I add the rows I want to the newerdata dataframe. There is probably a better way to do that. But it works. Here there is another thing that should probably be handled. Sometimes Google is not able to determine exactly what location we give it. There may be more than one place in a US state with the same name. I’m just taking the first result google gives me. A bit sloppy, I know. make some plots.

Finally I save the data. Now we should be ready to We are going to need some libraries for that:

library(grid)
library(gganimate)
library(animation)
library(ggplot2)
require(cowplot)

I’ll just make absolutely sure that the data is in the form I need it to be:

load("newestdata.rda")
newestdata <- newestdata[complete.cases(newestdata),]
newestdata <- na.omit(newestdata)
newestdata$year <- as.numeric(newestdata$year)
newestdata$lat <- as.numeric(newestdata$lat)
newestdata$lon <- as.numeric(newestdata$lon)

The coordinates and the year should be numeric. Any rows with missing values should be gone forever.

I get a map to plot on:

us <- get_map("USA", zoom = 4)

And, lets make the first plot:

g <-ggmap(us) +
  geom_point(aes(x=lon, y=lat), data=newestdata, color="red")
g

Theres a lot of red there. I would like to make an animation. The standard way to do it here, or at least the way I usually do it, would be to define a function, that plots what I want to plot as a function of the year. Like this:

singleyear <- function(ye){
  g <-ggmap(us) +
  geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<(ye)),], color="red")
 g
}

Now, when I make this function call, I should get a map with all the monuments erected before 1890:

singleyear(1890)

I’m missing the monuments erected in 1890. That is because I would like those to be a different color. Lets add to the function, and call it again:

 

 

 

singleyear <- function(ye){
  g <-ggmap(us) +
  geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<ye),], color="red")+
  geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year==ye),], color="blue")
 g
}
singleyear(1890)

Now, when I plot the map for a given year, all monuments from that year will appear as blue. And for the next year they’ll turn red.

 

 

Lets back up a bit here.

What I’m doing is this: I take the map I retrieved from Google, and plots it with the ggmap function. Then I add points with the geom_point function. I tell the function that the data is “newestdata”, and that the points should be placed at position x,y where x is the longitude, and y the latitude. The color should be red. I am however also telling that the data should be the part of newestdata, where year is smaller than the ye-value I provide to the function. In the next line, I add the same point, just for the part of the data where year is equal to the ye-value i provide to the function. And that the color should be blue.

What I am going to do later, is to call this function for all years from 1860 to 2017, and show those plots one after another. We will get at small movie, where new monuments turn up as blue spots. And then turn red in the next frame.

Everything becomes very red. One way to do something about this, would be to make the dots transparent. Let them fade out. When the monument is just erected, let the dot be blue. The year after, let it be a transparent red dot, the year after that, make it more transparent. Areas with a lot of monuments will still be pretty red, but the new monuments will be more visible. We’ll get at sort of heatmap, where the very red areas are places with at lot of monuments, and the not so red areas have fewer.

I’ll need to have a maximum and a minimum for the transparency. Defined as af function of the year I am plotting, and the year a monument was erected. I’m gonna add it to the dataframe before I plot it. This is the line:

maxp <- 0.8
minp <- 0.3
newestdata$alpha <- (maxp - minp)/(ye-1861)*(as.numeric(newestdata$year)-ye)+maxp

A new column, alpha, is added to the dataframe, and the difference between the year I am plotting, and the year of the row is normalised to the range 0.3 to 0.8. The point begins as blue, turns red with a transparency of 0.8, and will fade to 0.3.

I’m not sure the range is perfect. I’m gonna go with it for now.

Lets add it to the function:

maxp <- 0.8
minp <- 0.3
singleyear <- function(ye){
  newestdata$alpha <- (maxp - minp)/(ye-1861)*(as.numeric(newestdata$year)-ye)+maxp
  g <-ggmap(us) +
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<ye),], alpha=newestdata[which(newestdata$year<ye),]$alpha, color="red")+ 
  geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year==ye),], alpha=1, color="blue")
 g
}
singleyear(1890)

 

What I also added, was this: “alpha=newestdata[which(newestdata$year<(aar)),]$alpha”. Alpha is the transparency. So each point is plottet with the transparency I calculated.

What next? Lets adjust the plot a bit:

maxp <- 0.8
minp <- 0.3
singleyear <- function(ye){
  newestdata$alpha <- (maxp - minp)/(ye-1861)*(as.numeric(newestdata$year)-ye)+maxp
  g <-ggmap(us) +
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<ye),], alpha=newestdata[which(newestdata$year<ye),]$alpha, color="red")+ 
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year==ye),], alpha=1, color="blue") +
    theme(plot.title=element_text(hjust=0)) +
    theme(axis.ticks = element_blank(),
        axis.text = element_blank(),
        panel.border = element_blank()) +
    labs(title="Confederate memorials", subtitle=ye, x=" ", y="")
 g
}
singleyear(1890)

 

Labels are left aligned (hjust=0), tickmarks and text is removed (all the element_blank parts). And a title “Confederate memorials” is added, with a subtitle indicating the year we have reached in the plot.

Nice. What more? The interesting part, at least I think it is interesting, is the fact that the number of these memorials increase at certain times. That can be seen on the map. But a line-plot would probably make it more visible. It would also be nice to have it side-by-side with the map. How to do that?

To begin with, I’ll need a set of data with the cumulative count of monuments:

linedata <- as.data.frame(table(newestdata$year), stringsAsFactors = FALSE)

colnames(linedata) <- c("year", "cumsum")
linedata$year <- as.numeric(linedata$year)
linedata$cumsum <- cumsum(linedata$cumsum)

I make a new dataframe, linedata. The content is table(newestdata$year). The table function summarizes the data in newestdata. It gives me a table with all the years, and the number those years occur. Eg. that there are 7 occurences of the year 1870. That corresponds to 7 monuments erected in 1870. I save that as a dataframe. Then I change the names of the colums, and make sure that the years are saved as numeric. And then I call cumsum. That is a function that calculates the cumulative sum. Ie in 1861 2 monuments where erected. None where erected before that. The cumulative sum of all monuments until 1861 was 2. In 1862 3 monuments were erected. The cumulative sum for 1862 is 5. The sum of the 3 monuments erected that year, and all the monuments erected before that.

That in itself is an interesting plot:

ye = 2017
h <- ggplot(linedata[which(linedata$year<=ye),]) +
  geom_line(aes(x=year, y=cumsum))
h

 

Something happens in 1910 and again in 1950. Or somewhere close to those years. At least that is where the graph changes shape.

Lets adjust it a bit:

ye=2017
h <- ggplot(linedata[which(linedata$year<=ye),]) +
  geom_line(aes(x=year, y=cumsum))+
  xlim(1860,2017) +
  ylim(0,850) +
  ylab("Number of confederate memorials") +
  xlab("")
h

Nothing fancy, just freezing the axes, adding a label for the y-axis, and removing it from the x-axis.

Now I can add it to the original plot. I loaded the library cowplot earlier. That gives us the function plot_grid.

 

i <- plot_grid(g,h,align='h')
i

I have two plots, g and h, and combine them in a newplot, horizontally (thats the h in align), to a new plot i. And then I plot i.

 

 

Lets add that to the function:

singleyear <- function(ye){
  newestdata$alpha <- (maxp - minp)/(ye-1861)*(as.numeric(newestdata$year)-ye)+maxp
  g <-ggmap(us) +
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<ye),], alpha=newestdata[which(newestdata$year<ye),]$alpha, color="red")+ 
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year==ye),], alpha=1, color="blue") +
    theme(plot.title=element_text(hjust=0)) +
    theme(axis.ticks = element_blank(),
        axis.text = element_blank(),
        panel.border = element_blank()) +
    labs(title="Confederate memorials", subtitle=ye, x=" ", y="")
  h <- ggplot(linedata[which(linedata$year<=ye),]) +
    geom_line(aes(x=year, y=cumsum))+
    xlim(1860,2017) +
    ylim(0,850) +
    ylab("Number of confederate memorials") +
    xlab("")
  i <- plot_grid(g,h,align='h')
  i
}
singleyear(1890)

Now I’m getting there! Almost ready for the animation. I would like some annotation on the h-plot. A couple of arrows. And I would like them to show up in the animation. As in, from year 1910 there should be text at a certain place. But not before. It’s not that difficult.

If I add this:

if(ye>1908){

h <- h + annotate(“text”, x =1980, y = 200, label=“NAACP established”) + geom_segment(aes(x=1950, y = 200, xend=1909, yend = 200), size=1, arrow=arrow(length=unit(0.5, “cm”))) }

to the function, every plot, for a year after 1908, will have an annotation on the h-plot, at position 1980,200, with the text “NAACP established”. The next line, geom_segment, will add an arrow, with a size defined by the size and length parameters, beginning at 1950,200 and ending at 1909,200.

It took quite a bit of time to fiddle with those positions!

That is not the only annotation I want. So the complete function is as follows:

singleyear <- function(ye){
  newestdata$alpha <- (maxp - minp)/(ye-1861)*(as.numeric(newestdata$year)-ye)+maxp
  g <-ggmap(us) +
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year<ye),], alpha=newestdata[which(newestdata$year<ye),]$alpha, color="red")+ 
    geom_point(aes(x=lon, y=lat), data=newestdata[which(newestdata$year==ye),], alpha=1, color="blue") +
    theme(plot.title=element_text(hjust=0)) +
    theme(axis.ticks = element_blank(),
        axis.text = element_blank(),
        panel.border = element_blank()) +
    labs(title="Confederate memorials", subtitle=ye, x=" ", y="")
  h <- ggplot(linedata[which(linedata$year<=ye),]) +
    geom_line(aes(x=year, y=cumsum))+
    xlim(1860,2017) +
    ylim(0,850) +
    ylab("Number of confederate memorials") +
    xlab("")
  
  if(ye>1908){
    h <- h + annotate("text", x =1980, y = 200, label="NAACP established") +
       geom_segment(aes(x=1950, y = 200, xend=1909, yend = 200), size=1, arrow=arrow(length=unit(0.5, "cm")))
  }

  if(ye>1910){
    h <- h + annotate("text", x = 1870, y = 500, label="Coincidence?") +
      geom_segment(aes(x=1870, y=490, xend=1905, yend=240), size=1, arrow=arrow(length=unit(0.5, "cm")))
  }

  if(ye>1955){
    h <- h + annotate("text", x =1960, y = 400, label="Beginning of civil rights movement") +
      geom_segment(aes(x=1950, y = 450, xend=1955, yend = 640), size=1, arrow=arrow(length=unit(0.5, "cm")))
  }

  if(ye>1960){
    h <- h + annotate("text", x= 1866, y=515, label="Another") +
      geom_segment(aes(x=1882, y=500, xend=1955, yend=680), size=1, arrow=arrow(length=unit(0.5, "cm")))
  }

  i <- plot_grid(g,h,align='h')
  print(i)
}
singleyear(2017)
## Warning: Removed 1 rows containing missing values (geom_point).

OK. It looks like hell. But! In just a moment, it will look. Well, not exactly perfect, but at least much better.

The way to animate is this: Define a function that gives you the frames you want, as a function of an iterable. That’s already done. Call this:

saveGIF({
  for (ye in 1860:2017){
   singleyear(ye)
  }
}
, interval=0.3, ani.width=1280, ani.height=720)

Enjoy. Oh, and note, that it will crash if you try to animate it a notebook.

Hvor blev jeg af?

Tja. Godt spørgsmål. Der har været meget stille her i et stykke tid.

Det har der været før. Det bliver der sikkert igen. Denne gang har det nok været som reaktion på ændringer på min arbejdsplads. Der jo nok, hvis vi virkelig skal kigge dybt i sjælen, udløste noget der kunne ligne en depression. Ikke noget alvorligt. Afgjort ikke nok til at udløse piller. Men bare en længere periode med – “Fuck. Var det det?” tanker. Og ikke specielt systematiske overvejelser om hvad katten jeg så skulle bruge tiden på.

Det er jeg så nogenlunde ude af. Der begynder at være fod på tingene på hjemmefronten. Arbejdet er, nåja, arbejde. Det virker ikke så håbløst som det har gjort. Og jeg er nok ved at vænne mig til, at identitet er noget der skal findes andre steder end på jobbet. Ligesom anerkendelse og respekt.

Så med lidt held kommer der til at ske lidt mere her. Der er nørdeprojekter undervejs, faktisk godt i gang. Der er køkkenteknikker der skal afprøves. Der er politiske kæpheste der skal luftes. Og så er det fredag, og lige om lidt skal jeg i biografen og se præsentationen af noget et af de projekter jeg har gang i har kastet af sig.

Der findes ikke problemer

Kun udfordringer.

Det har jeg i hvert fald ladet mig fortælle. Og det handler jo om at man skal italesætte tingene på den rigtige måde. Problemer er negativt ladede. Måske endda så meget, at de ikke kan løses. Så det ord må vi ikke bruge. I stedet skal vi bruge ordet udfordring, der ikke er negativt ladet. Ordet udfordring, i stedet for udfordring, skal lissom signalere, at det er noget vi løser. Vi skal bare tage os sammen, så viser det sig at udfordringen slet ikke er en udfordring, men i stedet en udfordring.

Jeg tror bare der er en udfordring med at sige udfordring i stedet for udfordring. Udfordringen består i at vi gør sproget upræcist. Vi skaber en udfordring, når vi mener at skulle bruge et andet ord end udfordring, fordi udfordring er negativt. Du skal ikke komme med udfordringer, du skal komme med løsninger. Ja, selvfølgelig, men hvis jeg havde løsningen på den her udfordring, så ville jeg ikke have behov for at komme med den til dig. Så var det jo ikke en udfordring længere. Det kunne være at der så var en udfordring ved at gennemføre løsningen på udfordringen. Men det ville jo være en anden udfordring.

Udfordringen ved ikke at måtte kalde udfordringer for udfordringer er dobbelt.

Dels er der udfordringen ved at skulle kalde udfordringer for udfordringer i stedet for udfordringer. Det mudrer sproget. Hvis du har læst hertil, vil du vide hvad jeg mener. Det er faktisk udfordrende at skrive udfordring i stedet for udfordring hver gang, og det gør sikkert også at man er ret udfordret når man skal læse teksten. Det spænder ben for udfordringsløsningen hvis man ikke må kalde en udfordring for en udfordring. Det fjerner fokus på, at når vi løser denne udfordring, så er det faktisk fordi det er en udfordring, som er negativ, og derfor skal løses. Vi forfladiger udfordringsstillingen, og dermed bliver det også udfordrende at gennemskue hvori udfordringen egentlig består. Er det overhovedet en udfordring? Er vi sikre på at det ikke bare er en udfordring i stedet? Udfordringer er også noget man kan vælge at tage imod. Det ligger i ordet. Men ikke alle udfordringer er så u-udfordringsatiske, at man kan ignorere dem. Nogen udfordringer er så udfordrende, at man bliver nødt til at gøre noget ved dem.

Den anden udfordring er, at en udfordring jo ikke holder op med at være udfordrende, blot fordi vi kalder det for en udfordring i stedet for en udfordring. Det er lidt ligesom oprindeligt neutrale betegnelser for folk med høj koncentration af melanin i huden. De er ikke længere neutrale (altså betegnelserne), de er blevet negativt ladede. Det er de blevet på grund af racisme. Så nu bruger vi et andet ord, der er neutralt. Men racismen er ikke forsvundet, så om lidt er det nye, neutrale, ord lige så negativt ladet som det gamle, oprindeligt neutrale, men nu negative ord. Udfordring er et pænt nyt ord, som vi kan bruge i stedet for det grimme gamle ord, udfordring. Men fordi virkeligheden nu engang er sådan at udfordringer jo sjældent rent faktisk er positive – hvis de var det, var de jo ikke udfordringer, så virker det kun en kort tid. Inden vi ser os om, er udfordringer noget lige så negativt ladet som udfordringer oprindeligt var. Og så begynder vi at tale om muligheder i stedet for udfordringer.

Har du fået konstateret kræft? Det er ikke en udfordring. Det er nu en mulighed. Den skal du bare gribe. Og så bliver sproget endnu mere absurd. Når man ikke må sige udfordring, men skal sige mulighed i stedet for mulighed, så går det helt galt. Muligheder lyder pænt. Pænere end muligheder. Men fordi vi kalder det for en mulighed, er det jo ikke holdt op med at være en mulighed. Muligheden er stadig negativ. Nu er vi blot begyndt at bruge et ord der i endnu højere grad implicerer en valgmulighed. Så når jeg står med en mulighed, det kunne være at der er et vandrør der er sprunget, så skal jeg forsøge at se mulighedsløsninger i den mulighed jeg har fået fordi gulvet sejler. Min underbo har også fået en mulighed, idet der drypper vand ned fra hans loft. Der må han jo bare se det positive i at han nu har fået mulighed for at løse en mulighed.

Der er også den mulighed, at folk der får at vide at de skal sige mulighed i stedet for mulighed, muligvis begynder at føle sig lidt til grin. De betragter det som muligt at muligheder faktisk er muligstiske, og at de ikke bliver mindre negative af at vi siger mulighed i stedet for mulighed. Jeg er ret sikker på at min underbo vil kigge mærkeligt på mig når jeg fortæller ham at han har en mulighed når hans gulvtæppe er blevet vådt fordi jeg har en mulighed med en oversvømmelse i køkkenet. Muligvis er der en mulighed for at folk der bruger ordet mulighed i stedet for bare at kalde tingene ved deres rette navn, bliver lidt til grin. Eller at de ikke længere bliver taget seriøst. Jeg er ikke helt overbevist om at HR-chefen vil betragte det som en mulighed, hvis hans medarbejdere betragter ham med skepsis hver gang han siger mulighed.

Muligvis skal vi i virkeligheden forsøge at skære udfordringerne ind til benet, og erkende at det ikke kan afvises, at der faktisk er tale om et problem. Hive fat i værktøjskassen med redskaber til problemløsning. Og i stedet for bare at italesætte tingene på en måde der er i overensstemmelse med den sidste managementmode, rent faktisk løse problemerne.

 

Hjemmelavet Gin del II. Hvor kommer farven fra?

Jeg fik ikke rigtigt vist et billede af hvordan resultatet så ud i mit opslag om at lave sin egen gin. Lad os bare sige at det ligner en urinprøve.

Nu er gin normalt ikke farvet. Så hvor kommer farven fra? Fra tidligere eksperimenter med pebersnaps – de gentages ikke – ved jeg at peber kan farve ret godt. Men der kunne være tale om andre ting også. Så det måtte prøves.

Jeg skulle lave sous vide cheesecake alligevel, og det skal gøres ved samme temperatur som når der laves gin. Så det var oplagt lige at køre et lille eksperiment. Bare lidt hurtigt, for jeg var kommet lidt sent igang.

Her er forberedelsen. 

Fra venstre mod højre, 2 enebær, 2 peberkorn og 2 allehånde bær. Dertil 50 ml vodka. Det hele i mine små henkogningsglas, låg på, i vand (80 grader, 10 minutter). Det var i hvert fald planen. Noget gik galt med tidtagningen i appen til Anovaen. Så de fik snarere et kvarter end 10 minutter.

Det gør ikke noget. Det jeg er ude efter er at identificere hvilket krydderi der giver farven. Hvis det allehånde farver mere end de andre efter 15 minutter, så gør allehånde det også efter 10 minutter. Det er den relative forskel jeg er ude efter. Ja, der kan være forskelle hvis det egentlige farvestof ligger dybt i bærret, og først trækker ud sent i forløbet. Men det er så korte tidsrum vi har gang i her, at det næppe gør en forskel.

Og hvordan så det så ud? Således: 

Allehånde – stort set ingen farvning. Peber – en smule. Farven kommer helt klart fra enebærrene. Og da der skal ret mange af dem i, er der nok ingen vej uden om at min gin kommer til at ligne en urinprøve.

Hvorfor er gin så ikke farvet? Det er der også nogen mærker der er. Den primære forskel er nok, at det meste gin vi køber, er destilleret gin. Den rå sprit trækker med de urter der tilsættes. Og så destilleres den igen. Smagsstofferne følger med. Og hvis farvestofferne er tilstrækkeligt “tunge” og ikke følger med, ja så er der ikke meget farve på slutresultatet.

Klistermærker

I kender dem godt – i hvert fald hvis I er nørder. De der klistermærker man får i forskellige sammenhænge, til at sætte bag på sin computer.

Det er bare ikke altid man lige kan få dem man har brug for. Men så må man jo selv bestille dem. Så det gjorde jer. Inkscape hjalp med designet. Nemprint.dk hjalp med trykket (mod betaling). Og forleden kom de. Ret godt timet, men det er en anden historie, der ikke hører hjemme i et offentligt rum.

Lidt dyrt (3,42 stykket) men det var fordi jeg kun bestilte 100. OK kvalitet, ikke helt så tykke som de rigtig lækre, men så afgjort et sted jeg kunne finde på at bestille igen.

Det næste projekt

Men nok med en anden tekst. Der er lidt mere relevant. “Trust no one”. Eller måske lidt meta? “Hanlon’s razor”. Så kan folk selv google og gætte. Anyway – det tager lidt tid før jeg bliver færdig med den.