0

I have a simple dataframe where I have value in log2 scale.

I'm trying to add a column to the dataframe containing R,G,B code proportional to value in the first column. Also if the value is greater or lower than a value, I want to put a treshold. In this case min is -5 max 5. I did it in awk, but I don't find the solution in R My awk code :

awk -v maxlogratio=5 -v FS='\t' -v OFS='\t' '/^chr/{{r=0;g=0;if($9<1){{r=-255*(log($9)/log(2))/maxlogratio;if(r>255){{r=255}}}};if($9>1){{g=255*(log($9)/log(2))/maxlogratio;if(g>255){{g=255}}}};print $0,r","g",0"}}'

The only difference here is that my value where not in log2 scale (it's why I have log(x)/log(2)

R code:

activity_rgb=data.frame(activity=seq(from=-6,to = 6,by = 1))%>%
mutate(rgb=ifelse(activity<0,c(-255*(activity)/5,0,0),c(0,255*(activity),0)))

I got this :

   activity  rgb
1        -6  306
2        -5  255
3        -4  204
4        -3  153
5        -2  102
6        -1   51
7         0 -255
8         1    0
9         2  255
10        3  510
11        4  765
12        5 1020
13        6 1275

I expect this :

activity    rgb
  -6    255,0,0 
  -5    255,0,0 
  -4    204,0,0 
  -3    153,0,0
  -2    102,0,0
  -1    51,0,0
   0    0,0,0   
   1    0,51,0  
   2    0,102,0 
   3    0,153,0 
   4    0,204,0 
   5    0,255,0
   6    0,255,0 

So I need to paste value in column rgb, but I don't know how to do it.

So finally I did something to got r,g,b

But I still cannot fix the min and value to 255 for -5 / 5

activity_rgb=data.frame(activity=seq(from=-6,to = 6,by = 1))%>%
mutate(rgb=ifelse(activity<0,paste(-255*(activity)/5,0,0,sep = ","),paste(0,255*(activity)/5,0,sep = ",")))

activity_rgb
   activity     rgb
1        -6 306,0,0
2        -5 255,0,0
3        -4 204,0,0
4        -3 153,0,0
5        -2 102,0,0
6        -1  51,0,0
7         0   0,0,0
8         1  0,51,0
9         2 0,102,0
10        3 0,153,0
11        4 0,204,0
12        5 0,255,0
13        6 0,306,0 
Nono_sad
  • 425
  • 2
  • 12
  • 1
    https://stackoverflow.com/q/13353213/3358272 suggests the use of `colorRampPalette`. Since you seem to want *bins* of colors, perhaps you can use `cut` to first bin your data values and then choose the appropriate colors. – r2evans Apr 08 '20 at 23:18
  • Thanks, but actually I need the RGB value in a column of my dataframe. Thanks – Nono_sad Apr 08 '20 at 23:21
  • The rgb value you have in your column is ... not directly useful. Why not use something that provides a value that is directly applicable in all plotting functions without you have to string-split/parse/breakup/munge it? – r2evans Apr 08 '20 at 23:36
  • 1
    Because then I'll write the output dataframe in a file and use a program which need RGB value – Nono_sad Apr 08 '20 at 23:37

1 Answers1

1

Try this use of colorRamp:

scale_to_rgb <- function(val, bounds = NA,
                         colors = c("#ff0000", "#000000", "#00ff00"),
                         format = c("rgb", "comma"), ...) {
  if (anyNA(bounds)) bounds <- range(val, na.rm = TRUE)
  format = match.arg(format)

  isna <- is.na(val)
  ispos <- !isna & val >= 0
  isneg <- !isna & !ispos
  cols <- matrix(NA, nrow = length(val), ncol = 3)
  valneg <- pmax(bounds[1], val[isneg]) / bounds[1]
  valpos <- pmin(bounds[2], val[ispos]) / bounds[2]

  cols[isneg,] <- colorRamp(colors[2:1], ...)(valneg)
  cols[ispos,] <- colorRamp(colors[2:3], ...)(valpos)

  if (format == "rgb") {
    cols <- cols / 255
    rgb(cols[,1], cols[,2], cols[,3])
  } else {
    cols <- round(cols, 0)
    paste(cols[,1], cols[,2], cols[,3], sep = ",")
  }
}

This now allows you to pass arguments through to colorRamp, namely bias=, space=, interpolate=, and alpha=. Feel free to experiment with them.

Your data, and a dplyr use-case:

activity_rgb <- data.frame(activity = seq(from=-6, to = 6, by = 1))

activity_rgb %>%
  mutate(
    rgb = scale_to_rgb(activity, bounds = c(-5, 5)),
    comma = scale_to_rgb(activity, bounds = c(-5, 5), format = "comma")
  )
#    activity     rgb   comma
# 1        -6 #FF0000 255,0,0
# 2        -5 #FF0000 255,0,0
# 3        -4 #CC0000 204,0,0
# 4        -3 #990000 153,0,0
# 5        -2 #660000 102,0,0
# 6        -1 #330000  51,0,0
# 7         0 #000000   0,0,0
# 8         1 #003300  0,51,0
# 9         2 #006600 0,102,0
# 10        3 #009900 0,153,0
# 11        4 #00CC00 0,204,0
# 12        5 #00FF00 0,255,0
# 13        6 #00FF00 0,255,0

You've mentioned what rescaling from -4 to 5 would do ...

activity_rgb %>%
  mutate(
    rgb = scale_to_rgb(activity, bounds = c(-4, 5)),
    comma = scale_to_rgb(activity, bounds = c(-4, 5), format = "comma")
  )
#    activity     rgb   comma
# 1        -6 #FF0000 255,0,0
# 2        -5 #FF0000 255,0,0
# 3        -4 #FF0000 255,0,0
# 4        -3 #BF0000 191,0,0
# 5        -2 #800000 128,0,0
# 6        -1 #400000  64,0,0
# 7         0 #000000   0,0,0
# 8         1 #003300  0,51,0
# 9         2 #006600 0,102,0
# 10        3 #009900 0,153,0
# 11        4 #00CC00 0,204,0
# 12        5 #00FF00 0,255,0
# 13        6 #00FF00 0,255,0
r2evans
  • 108,754
  • 5
  • 72
  • 122
  • Thanks, but I expect to go from red to black then black to green. At 0 I expect rgb = 0,0,0 – Nono_sad Apr 08 '20 at 23:56
  • It's ok I find. I just have to add black color in the function. Thank you – Nono_sad Apr 09 '20 at 00:04
  • Any other unspoken constraints in your requirements? (set `colors=c("#ff0000", "#000000", "#00ff00")`) – r2evans Apr 09 '20 at 00:04
  • pmax and pmin works find if I have value greater and lower than -5 / 5; But If I have -4 it will scale it to -5 and I'll got 255,0,0 instead of 204,0,0 – Nono_sad Apr 09 '20 at 00:28
  • Your answer was very helpful but I'm still stuck on this. if I have value greater than -5 it will scale it to -5. Thanks you – Nono_sad Apr 09 '20 at 09:30
  • `pmax(-5, pmin(5, seq(-4, 6)))` does not scale the -4 to -5, it stays as a -4. – r2evans Apr 09 '20 at 16:15
  • at -4 r will be 255, I expect 204. Because -4 it's the minimum in this case so the scale go to -4 to 5 – Nono_sad Apr 09 '20 at 21:01
  • I have no idea what you're talking about here, Nono_sad, but take a look at my recent edit. The new approach color-ramps separately for positive and negative numbers (centered on zero). It does hard-code and assume `0` as the center point, I'll leave it as an exercise if you want/need to change that. – r2evans Apr 09 '20 at 21:36
  • It's exactly what I was looking for ! Thank you so much – Nono_sad Apr 10 '20 at 21:27