Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ggplot2: Plotting Andrews curves

I am trying to learn about Andrews plots by doing. I know R has the andrews package that uses the base plotting system, but I wanted to use ggplot2. I followed the andrews_curves function implementation in the pandas library.

I have managed to translate the data transformation steps of the Python function:

andrews <- function(df, class_column, samples=200) {
  t <- seq(-pi, pi, length.out = samples) 
  
  vals <- t(
    data.matrix(                    
      df[, -which(names(df) %in% class_column)]
    )
  )
  
  curves <- outer(vals[1, ], rep(1, length(t))) 
  
  for (i in 2:nrow(vals)) {                       
    ft = (i %/% 2) * t                          
    if (i %% 2 == 0) {                                
      curves <- curves + outer(vals[i, ], sin(ft))
    } else {
      curves <- curves + outer(vals[i, ], cos(ft))
    }  
  }
  
  df_out <- data.frame( 
    t = rep(seq_len(samples), nrow(curves)),
    sample =  rep(seq_len(nrow(curves)), ncol(curves)),
    values = as.vector(t(curves)),
    class_column = rep(df[, class_column], samples)
  ) 
  
  df_out 
} 

Unfortunately, I am a beginner in Python and I do not understand the plotting systems (60-69 in andrews_curves). I just want to replicate this plot by using the same data, but I get something that is really off:

iris <- read.csv('https://raw.github.com/pandas-dev/pandas/main/pandas/tests/io/data/csv/iris.csv')

adrews_data <- andrews(iris, "Name", 30)

library(ggplot2)
ggplot(adrews_data, aes(x = t, y = values, color = class_column, group = interaction(class_column, sample))) +   
  geom_line(size = 1.2)

Created on 2022-01-26 by the reprex package (v2.0.1)

like image 452
Claudiu Papasteri Avatar asked Aug 30 '26 17:08

Claudiu Papasteri


2 Answers

The code below seems to do what you want and, by eye, matches the plot on the Wikibedia page to which you link.

The only awkward piece was duplicating each row of the iris data frame for each value of T between -pi and pi. It's critical that the value passed to uncount() is the same as the length of the vector t.

I've taken the order of the columns in the formula for the Andrews score of each row to be the order in which they appear in the iris data frame. I don't know what effect changing the order of the columns would have.

library(tidyverse)

t=seq(-pi, pi, length.out=100)
root2 <- sqrt(2)
as_tibble(iris) %>%
  mutate(Row=row_number(), .before=1) %>% 
  uncount(100) %>% 
  add_column(T=rep(t, times=nrow(iris))) %>% 
  mutate(Andrews=Sepal.Length/root2 + Sepal.Width*sin(t) + Petal.Length*cos(t) + Petal.Width*sin(2*t)) %>% 
  ggplot() +
    geom_line(aes(x=T, y=Andrews, group=Row, colour=Species))

giving

enter image description here

group=Row produces one line in the plot for each row of the data frame. colour=Species colours each line according to the corresponding value of Species.

[There's probably a neater way than uncount() %>% add_column() of replicating each row of iris for each value of t using expand(), but I couldn't get it to work.]

like image 57
Limey Avatar answered Sep 01 '26 06:09

Limey


Seems to me that the oversight is that matrices in R are column-major and in python are row-major. In addition, you had hard-coded the iris dataset in your df_out specification whereas it was not a formal argument to the function.

library(ggplot2)

andrews <- function(df, class_column, samples=200) {
  t <- seq(-pi, pi, length.out = samples) 
  
  vals <- t(
    data.matrix(                    
      df[, -which(names(df) %in% class_column)]
    )
  )
  
  curves <- outer(vals[1, ], rep(1, length(t))) 
  
  for (i in 2:nrow(vals)) {                       
    ft = (i %/% 2) * t                          
    if (i %% 2 == 0) {                                
      curves <- curves + outer(vals[i, ], sin(ft))
    } else {
      curves <- curves + outer(vals[i, ], cos(ft))
    }  
  }
  
  row <- as.vector(row(curves))
  col <- as.vector(col(curves))
  
  df_out <- data.frame(
    t = col,
    sample = row,
    values = as.vector(curves),
    class_column = df[[class_column]][row]
  )
  
  df_out 
} 

df <- andrews(iris, "Species")

ggplot(df, aes(x = t, y = values, color = class_column, 
               group = sample)) +   
  geom_line()

Created on 2022-01-26 by the reprex package (v2.0.1)

like image 39
teunbrand Avatar answered Sep 01 '26 05:09

teunbrand



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!