Showing posts with label R. Show all posts
Showing posts with label R. Show all posts

Saturday, 19 November 2022

VOLCANO PLOTS CUSTOMIZATION IN R

 

Volcano plots became famous with big data analysis. Whether you are working with NGS, mass spectrometry data or similar, you might need to build this kind of plot.



The 'ggplot2' package is very useful for this purpose. I found a very nice post where instructions to build a basic volcano plot are provided, just have a look. I will start from there and add some lines of code to customize the plot, including how adding the lines of significance and highlighting specific genes of interest.

It’s gonna be a bit long guiding you step-by-step, but I hope you’ll find it useful!


Let's start with the packages:

library(dplyr)
library(ggplot2)
library (ggrepel)
library(dslabs)


From the post I mentioned, I’ll use this function to have a nicer output for showing data (it is slightly modified compared to the original):

knitr_table <- function(x) {
                   x %>%
                   knitr::kable(format = "rst", digits = Inf,
                   format.args = list(big.mark = ","))
                            }


… and also the source of data:

data <- read_tsv("https://raw.githubusercontent.com/sdgamboa/misc_datasets/master/L0_vs_L20.tsv")

dim(data) #it shows the data dimensions (rows, columns)

head(data) %>%
    knitr_table() #to show the first 6 lines





These are analysed data from RNA sequencing, so you can find the variables of interest for building a volcano plot:

Genes: it contains the names of each gene

logFC: it contains the logarithm base 2 (log2) of the fold change of each gene

PValue: it contains p-values for each comparison

FDR: false discovery rate


We will use the FDR column, whose values need to be converted to their negative logarithm base 10 [log10(FDR)]. We’ll do it later, in the ‘ggplot()’ function.



DIFFERENTIALLY EXPRESSED GENES (DEGs)


Now that we got the data, it is necessary to add a column with the indication about the differentially expressed genes (DEGs). Usually, they are those with an FDR equal to or lower than 0.05, and a fold change equal to or higher than 2.

Therefore, we’ll add the variable “Expression” where classifying the genes as up-/down-regulated or unchanged according to the FDR and fold change:

data$Expression <- 
        ifelse((data$FDR <= 0.05 & data$logFC <= -1), "Down-regulated", 
                ifelse((data$FDR <= 0.05 & data$logFC >= 1), "Up-regulated",
                "Unchanged"))               

Where 1 is the log2(2).

Now, it is possible to count how many genes are up-/down-regulated or unchanged:

data %>%
    count(Expression)
    knitr_table()



… as well as to get a data frame with up-regulated genes and down-regulated genes, in descending order based on logFC. It can be exported if needed:

up <- data %>%
           filter(Expression == "Up-regulated") %>%
           arrange(desc(logFC))

down <- data %>%
           filter(Expression == "Down-regulated") %>%
           arrange(desc(abs(logFC)))

head(up) %>%
    knitr_table()

library(rio)
export(up, "up-regulated genes.xlsx") #to export the table to an excel file in the working directory

You can do the same with down-regulated genes.


BUILDING THE BASIC VOLCANO PLOT


Now we will build the basic volcano plot, remembering to convert the FDR to its log base 10 and assigning the colour to “Expression”:

p1 <- ggplot(data, aes(logFC, -log(FDR,10))) +
      geom_point(aes(color = Expression), size = 1.8, shape = 19, alpha = 0.3) +
      xlab(expression("log"[2]*"FC")) +
      ylab(expression("-log"[10]*"FDR")) + 
      scale_color_manual(values = c("deeppink1","orangered", "gray50")) +
      theme_bw()

windows(width=10, height=7); par(lwd=1, las=1, family="sans", cex=1,
  mgp=c(3.0,1,0))

p1




ADDING THE LINES FOR SIGNIFICANCE


We first have to set the values for the lines on the x and y axes:

y <- -log(0.05,10)  #this is for the FDR, the log base 10 of 0.05

x <- log(2, 2)  #this is for the fold change, the log base 2 of 2

p2 <- p1 +

      geom_hline(yintercept = y, col = "black", linetype = "dotdash") + #add an horizontal line at point y
      geom_vline(xintercept = c(x, -x), col = "black", linetype = "dotdash") #add a vertical line at point                                                                                        x and -x

windows(width=10, height=7); par(lwd=1, las=1, family="sans", cex=1,
  mgp=c(3.0,1,0))

p2



It calls to mind Japanese cherry blossom, doesn't it?

As a final step, you might want to highlight some genes of interest and add the labels.  



SELECTED GENES


Let's select those genes with 'filter()' in the 'dlpyr' package: 

labels <- data %>%
          filter(Genes == "57_45" | Genes == "1092_3" | Genes == "144_7" |
                 Genes == "468_3" | Genes == "29_188" | Genes == "1192_1")

Now we'll over-write them in the plot we built: 

windows(width=10, height=7); par(lwd=1, las=1, family="sans", cex=1,
  mgp=c(3.0,1,0))

p2 +
  geom_point(data = labels,
             mapping = aes(logFC, -log(FDR,10)), size = 1.8) + #this is for the points
  geom_label_repel(data = labels,
                   mapping = aes(logFC, -log(FDR,10), label = Genes), size = 2) #this is for the labels





Basically, inside the ‘geom_point()’ or the ‘geom_label_repel()’ the data you specify are the ones included in the previously prepared subset ‘labels’ and the mapping corresponds to the x and y of each point.

The ‘geom_label_repel()’ function, will move slightly the labels to avoid their overlapping. You can change the colour, size and shape of the highlighted points with the specific arguments in ’geom_point()’.



TOP GENES

To highlight the top genes we can use the ordered up-regulated and down-regulated genes we previously saved in the objects “up” and “down”.

We’ll select only the top 10 genes for up-regulated and down-regulated ones, and put them together in a data frame:

top_genes <- bind_rows(up %>% head(n=10),
                       down %>% head(n=10))

top_genes %>%
    knitr_table()

Now we can use this data frame similarly to the previous example: 

windows(width=10, height=7); par(lwd=1, las=1, family="sans", cex=1,
  mgp=c(3.0,1,0))

p2 +
  geom_point(data = top_genes,
             mapping = aes(logFC, -log(FDR,10)), size = 1.8) + 
  geom_label_repel(data = top_genes,
             mapping = aes(logFC, -log(FDR,10), label = Genes), size = 2)


 



SINGLE GENE


Finally, it is possible to highlight a single gene in this way:

windows(width=10, height=7); par(lwd=1, las=1, family="sans", cex=1,
  mgp=c(3.0,1,0))

p2 +
  geom_point(data = subset(data, Genes == "144_7"),
             mapping = aes(logFC, -log(FDR,10)), size = 1.8) +
  geom_label_repel(data = subset(data, Genes == "144_7"),
                   mapping = aes(logFC, -log(FDR,10), label = Genes), size = 2)




I hope this post will be useful as a kick-start. You can further customize the plot according to your needs, you have plenty of options. 
Have fun!

@colour credits: Peruzzella&Cinderella®


Sunday, 11 April 2021

BUILDING DOT PLOTS IN R SIMILAR TO THOSE WITH GraphPad

 

Have you ever had the necessity to build a dot plot in R that looks like the nice ones obtained with GraphPad?


Here, an example of what I am talking about:



To show you the solution I found, I'll use the chickwts preloaded dataset and ggplot2.

First, we'll load the packages and data needed:

library(ggplot2)
library(dplyr)
data(chickwts)  #the dataset shows chicken weights depending on food type 


In our example, we'll shorten the dataset to show only the weights in the sunflower and soybean group:

Dataset <- chickwts %>%
   filter(feed == "sunflower" | feed == "soybean")


Using the geom_dotplot() function, we can build the following graph:




As you can see, it shows the individuals as dots, coloured by category, with a black bar indicating the mean of the group.
Here, you have the code:

Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 1, #stackratio moves the dots                                                                                       #towards the left or the right
  dotsize = 0.7, binpositions = "all") +
stat_summary(fun = mean, geom = "crossbar", width = 0.7) +  #width change the length of the bar
theme_bw()



If you want to change the colour of the bar, you can do it by adding the colour argument in stat_summary().
In the following example I gave it the same colour of the dots:

Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_dotplot(binaxis = "y", stackdir = "center", stackratio = 1, 
  dotsize = 0.7, binpositions = "all") +
stat_summary(fun = mean, geom = "crossbar", width = 0.7, 
col = c("#9E0142","#3288BD")) +
scale_fill_manual(values = c("#9E0142","#3288BD")) +
theme_bw()







Arrived at this point, you could be still disappointed because the graph is not that similar to the original one.
So, since in geom_dotplot() is not possible to change the shape of dots, what to do?

The geom_jitter() function can help us:

Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_jitter(aes(shape = feed, col = feed), size = 2.5, width = 0.1)+
stat_summary(fun = mean, geom = "crossbar", width = 0.7,
col = c("#9E0142","#3288BD")) +
scale_fill_manual(values = c("#9E0142","#3288BD")) +
scale_colour_manual(values = c("#9E0142","#3288BD")) +
theme_bw()


Changing the width argument in geom_jitter() you can move a bit the points, so they do not overlap. 
The use of both scale_fill_manual() and scale_colour_manual() allows giving the same colour to the legend and dots.


Although it is not exactly the same, it looks pretty similar to the first one, doesn't it?


And now the final customization.
Perhaps you would like to choose the symbols displayed. In this case, you can add a layer with scale_shape_manual():

Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_jitter(aes(shape = feed, col = feed), size = 2.5, width = 0.1)+
stat_summary(fun = mean, geom = "crossbar", width = 0.7,
 col = c("#9E0142","#3288BD")) +
scale_fill_manual(values = c("#9E0142","#3288BD")) +
scale_colour_manual(values = c("#9E0142","#3288BD")) +
        scale_shape_manual(values = c(18, 25)) +
theme_bw()



You can find the symbols used in R here.

I hope you found this post useful.
As usual, if you have a better solution, please let me know!

Monday, 18 December 2017

LITTLE TROUBLESHOOTING IN R

Using R, little troubles can arise. Below, a "work in progress" collection of solved problems...or not yet...


If you have your own solution or wish to discuss some topics, your contribution will be welcome.


  • ERRONEOUSLY SAVED WORKSPACE


If you want to delete the workspace you saved, check into the Documents folder and delete both files that appear as "R Workspace" and "Rhistory". That's all!!







  • FISHER'S EXACT TEST ERROR MESSAGES: "FEXACT error 6 and 7"

Have you ever got error messages performing Fisher's exact test? Well, I have got two different error messages that I solved with the same "tricks":

  Error in fisher.test(.Table) : FEXACT error 7.
  LDSTP is too small for this problem.
  Try increasing the size of the workspace.

OR

  Error in fisher.test(.Table) : FEXACT error 6.
  LDKEY is too small for this problem.
  Try increasing the size of the workspace.


As suggested, you can first try to increase the workspace to 2e8 or 2e9: 

  fisher.test(data,workspace=2e8)  #change data with the name of your table


If it still doesn't work, you can go for a simulation:

  fisher.test(data,simulate.p.value=TRUE,B=1e7)

where simulate.p.value is a logical for computing p-value by Monte Carlo simulation and B indicates the number of replicates (by default B=2000), that can be changed as needed.
I know, it is not exactly the same...but at least you can run the analysis.

As usual, if you have a better solution, please let me know!




  • NEW VERSION NOT WRITABLE: " '/R-X.X.X/library' is not writable"

Have you just updated the R version but can't update the library because the folder is not writable?

Don't worry! You have two different options:

1. the easiest way is to create a private library. Just say "Yes" when R will ask you (and take note of the location of the folder);

2. if you still want to write in the library folder, you can get the authorisation following the easy instructions I provided in this post.




  • ANOVA ERROR MESSAGES: "Error in eigen" OR "Error in contrasts"

If you perform a mixed ANOVA, either using
aov() or anova_test() (from rstatix package), you could run into these error messages:

  Error in eigen(SSPE, only.values = TRUE) : 
  infinite or missing values in 'x'

OR

  Error in `contrasts<-`(`*tmp*`, value = contr.funs[1 + isOF[nn]]) : 
  contrasts can be applied only to factors with 2 or more levels


In both cases, the problem is represented by missing values (NA).

If you prepare your data excluding NA for your dependent variable with filter(), you will get the first message because there will be no values corresponding to that case in that particular level of the independent variable.

If you decide to solve the problem by eliminating the row with the NA from the dataset, you will get the second error message because R will miss completely that case in that particular level of the independent variable.

To avoid this problem you should remove the rows with that specific case at any level of the independent variable, including those where the value is not missing.

Of course, this can represent a loss of data.

One easy possibility is to substitute the NA with the mean of the group at that level of the independent variable.

However, to decide how to handle missing values, I suggest going on with further readings.





Monday, 19 June 2017

TWO-PROPORTION Z-TEST IN R



Recently I needed to compare two proportions using R. I tried with the prop.test  function, but I noticed that it performs a Chi square test. Unfortunately, it seems that there is not a function in R for two-proportion z-test and I had to write it on my own.

I  found only a post in R bloggers , in which the function allows to calculate the z-score. Hence, also using in part a function for one-proportion z-test,  I tried to improve it for getting the estimate of the two proportions, the p-value and the confidence interval (95%) for the difference.



Given that:



where:

      p1= proportion of subjects with the characteristic of interest in the 1st group (x1/n1)
      p2= proportion of subjects with the characteristic of interest in the 2nd group (x2/n2)


and:

Confidence interval of the difference:


where SE of the difference:



The function becomes:

z.prop = function(x1,x2,n1,n2,conf.level=0.95,alternative="two.sided"){
  numerator = (x1/n1) - (x2/n2)
  p.common = (x1+x2) / (n1+n2)
  denominator = sqrt(p.common * (1-p.common) * (1/n1 + 1/n2))
  z.prop.ris = numerator / denominator
  p1 <- x1/n1
  p2 <- x2/n2
  p.val <- 2*pnorm(-abs(z.prop.ris))
  if(alternative=="lower") {
            p.val <- pnorm(z.prop.ris)
         }
         if(alternative=="greater") {
            p.val <- 1 - (pnorm(z.prop.ris))
         }
  SE.diff <- sqrt(((p1*(1-p1)/n1)+(p2*(1-p2)/n2)))
  diff.prop <- (p1-p2)
  CI <- (p1-p2)+ c(
          -1*((qnorm(((1 - conf.level)/2) + conf.level))*SE.diff),
         ((qnorm(((1 - conf.level)/2) + conf.level))*SE.diff) )

  return (list(estimate=c(p1,p2),ts.z=z.prop.ris,p.val=p.val,diff.prop=diff.prop,CI=CI))

}



It gives you back a two-tailed p-value when the alternative is not specified.

For example, let’s say that you want to compare the proportion of Helicobacter infection in two groups of individuals: the first is composed by people with gastritis and the second one by healthy individuals.

Group 1 (gastritis): n1=500
Group 2 (healthy): n2=470

Group 1 with Helicobacter infection: x1=350
Group2 with Helicobacter infection: x2=295

It will be:

z.prop(350, 295, 500, 470)   #two-sided


This is what you get back:

$estimate
[1] 0.7000000 0.6276596

$ts.z
[1] 2.385499

$p.val
[1] 0.01705595

$diff.prop
[1] 0.07234043

$CI
[1] 0.0129810 0.1316999


You can also specify the alternative (“lower” or “greater”) for performing  one-tailed test.
Let me know if it works for you or how to improve it.