Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent add.text behavior on panels when grouping terms are used (y ~ x | g) #138

Open
stefaneng opened this issue Jul 15, 2023 · 6 comments

Comments

@stefaneng
Copy link
Contributor

stefaneng commented Jul 15, 2023

Related to #137

add.text has different behavior in a few different plotting functions. In particular, create.boxplot differs in behavior from most of the others.

Boxplot cannot add multiple text labels

A boxplot with multiple text labels and text.x or text.y will only draw the the first label but replicated for each of the coordinates.

library(BoutrosLab.plotting.general);

set.seed(779);
groupA <- rnorm(n = 100, mean = 10, sd = 2);
groupB <- rnorm(n = 134, mean = 10.5, sd = 2);

# Create data frame for plotting
to.plot <- data.frame(
    y = rep(
        c('1', '2'),
        times = c(100, 134)
        ),
    x = c(groupA, groupB)
    );

to.plot.groups <- rbind(
  data.frame(to.plot, z = 1),
  data.frame(to.plot, z = 2)
  )
to.plot.groups$z <- as.factor(to.plot.groups$z);

create.boxplot(
    formula = x ~ y,
    data = to.plot.groups,
    add.stripplot = TRUE,
    add.text = TRUE,
    text.labels = c('A', 'B'),
    text.x = c(1, 2),
    text.y = 15.3,
    text.col = 'black',
    text.cex = 1.5,
    text.fontface = 'bold',
    ylimits = c(
        min(to.plot$x) - abs(min(to.plot$x) * 0.1),
        max(to.plot$x) + abs(max(to.plot$x) * 0.1)
        )
    );
#> Warning in validDetails.text(x): NAs introduced by coercion

Boxplots grouped by a variable will put text in different panels

Only if a grouping variable is used will boxplot place one text label per each panel. This is actually opposite to what other plots do but this behavior seems the most intuitive.

# Boxplots grouped by a variable will put text in different panels
# if text.labels is a vector
create.boxplot(
    formula = x ~ y | z,
    data = to.plot.groups,
    add.stripplot = TRUE,
    add.text = TRUE,
    text.labels = c('A', 'B'),
    text.x = 1,
    text.y = 15.3,
    text.col = 'black',
    text.cex = 1.5,
    text.fontface = 'bold',
    ylimits = c(
        min(to.plot$x) - abs(min(to.plot$x) * 0.1),
        max(to.plot$x) + abs(max(to.plot$x) * 0.1)
        )
    );
#> Warning in validDetails.text(x): NAs introduced by coercion

#> Warning in validDetails.text(x): NAs introduced by coercion

Scatterplots draw multiple text labels as expected

Without grouping variables scatterplots draw on a single panel as expected with vector arguments.

to.plot.scatter <- data.frame(
  x = rnorm(100),
  y = rnorm(100),
  z = c(1,2)
  );
to.plot.scatter$z <- as.factor(to.plot.scatter$z);

create.scatterplot(
  y ~ x,
  data = to.plot.scatter,
  add.text = TRUE,
  text.labels = c('a', 'b'),
  text.x = c(1, 2),
  text.y = 3
  );

Scatterplots with groups place same text on both panels

Scatterplots will draw the same text on each panel, following the same behavior as plot without groups. This is usually not what is desired and is somewhat counter-intuitive.

# Groups place the same text on both panels
(group.scatter.text <- create.scatterplot(
  y ~ x | z,
  data = to.plot.scatter,
  add.text = TRUE,
  text.labels = c('a', 'b'),
  text.x = c(1, 2),
  text.y = 3,
  xlimits = c(-5, 5),
  ylimits = c(-5, 5)
  ))

Solution: Add different text to each panel

A solution to allow for text to be added to each panel separately is to manually create a latticeExtra::layer with lattice::panel.text. We can access the panel number with panel.number() and can create a vector of labels to index.

# A fix is to add text manually
my.panel.text <- c('panel 1', 'panel 2');

group.scatter.text + layer({
  panel.text(
    # Access panel number with panel.number()
    labels = my.panel.text[panel.number()],
    x = -3,
    y = -3
    )
  });

Sometimes we don't want to specify x and y coordinates but want to specify on a [0,1] scale. In this case we can use panel.key and the coordinates with corner.

# Using panel.key allows for 'corner' specification [0-1]
group.scatter.text + layer({
    panel.key(
        text = my.panel.text[[panel.number()]],
        points = FALSE,
        corner = c(0.05, 0.95)
        )
  });

Created on 2023-07-15 with reprex v2.0.2

@jarbet
Copy link
Contributor

jarbet commented Jan 26, 2024

There is a strange issue where @stefaneng's solution for adding text to different panels of create.scatterplot does not work when calling create.scatterplot and defining my.panel.text within a larger wrapper function (e.g. making a new function for an R package):

suppressPackageStartupMessages(library(BoutrosLab.plotting.general));
to.plot.scatter <- data.frame(
  x = rnorm(100),
  y = rnorm(100),
  z = c(1,2)
  );
to.plot.scatter$z <- as.factor(to.plot.scatter$z);

wrapper <- function() {
    group.scatter.text <- create.scatterplot(
      y ~ x | z,
      data = to.plot.scatter
      );

    my.panel.text <- c('panel 1', 'panel 2');

    group.scatter.text + layer({
        panel.key(
            text = my.panel.text[[panel.number()]],
            points = FALSE,
            corner = c(0.05, 0.95)
            )
        });
    }
wrapper();

Created on 2024-01-25 with reprex v2.0.2

@stefaneng
Copy link
Contributor Author

Hey @jarbet ! layers/panels are weird and don't have a standard environment so you need to pass the data via the data argument. I don't remember the specifics of how the layer environment works but this will definitely allow you to pass the info you need from the function to the plot.

suppressPackageStartupMessages(library(BoutrosLab.plotting.general));
to.plot.scatter <- data.frame(
  x = rnorm(100),
  y = rnorm(100),
  z = c(1,2)
);
to.plot.scatter$z <- as.factor(to.plot.scatter$z);

wrapper <- function() {
  group.scatter.text <- create.scatterplot(
    y ~ x | z,
    data = to.plot.scatter
  );
  
  my.panel.text <- c('panel 1', 'panel 2');
  
  group.scatter.text + layer({
    panel.key(
      text = my.panel.text[[panel.number()]],
      points = FALSE,
      corner = c(0.05, 0.95)
    )
  }, data = list(my.panel.text = my.panel.text));
}
wrapper()

Created on 2024-01-25 with reprex v2.1.0

@jarbet
Copy link
Contributor

jarbet commented Jan 30, 2024

Thanks @stefaneng, this works great! Hope school is going well!!

@jarbet
Copy link
Contributor

jarbet commented Mar 15, 2024

@stefaneng thanks again for your help with the above problem! I'm curious if you have any ideas for how to put text above/below bars in create.barplot when having multiple panels?

At a minimum, I know I can separately make each plot and add the text using text.above.bars, then combine using create.multipanelplot. However, I'm wondering if you think it is possible to more directly add the text when using | in the create.barplot formula?

For example, putting qvalue above the bars in each panel?

suppressPackageStartupMessages(library(BoutrosLab.plotting.general));

set.seed(123);
dataset <- data.frame(
    feature = c(rep('a', 3), rep('b', 3)),
    group = rep(1:3, 2),
    rho = runif(6, -1, 1),
    qvalue = runif(6, 0, 1)
    );

# how to put q-values above bars in each panel?
create.barplot(
    formula = rho ~ group | feature,
    data = dataset
    );

Created on 2024-03-14 with reprex v2.0.2

@stefaneng
Copy link
Contributor Author

There is probably a better way to do this via lattice... but almost the same solution as the other one is to split by the feature variable and pass the vector into each panel:

suppressPackageStartupMessages(library(BoutrosLab.plotting.general));

set.seed(123);
dataset <- data.frame(
  feature = c(rep('a', 3), rep('b', 3)),
  group = rep(1:3, 2),
  rho = runif(6, -1, 1),
  qvalue = runif(6, 0, 1)
);

# Create a lattice barplot with q-values above bars grouped by feature rho ~ group | feature
qvalue_feature_split <- split(round(dataset$qvalue, 2), dataset$feature)

create.barplot(
  formula = rho ~ group | feature,
  data = dataset,
  ylimits = c(-1.2, 1.2)
) + layer({
  ltext(x, y + sign(y) * 0.15, labels = qvalue_feature_split[[panel.number()]])
}, data = list(qvalue_feature_split = qvalue_feature_split))

Created on 2024-03-15 with reprex v2.1.0

@jarbet
Copy link
Contributor

jarbet commented Mar 15, 2024

There is probably a better way to do this via lattice... but almost the same solution as the other one is to split by the feature variable and pass the vector into each panel:

suppressPackageStartupMessages(library(BoutrosLab.plotting.general));

set.seed(123);
dataset <- data.frame(
  feature = c(rep('a', 3), rep('b', 3)),
  group = rep(1:3, 2),
  rho = runif(6, -1, 1),
  qvalue = runif(6, 0, 1)
);

# Create a lattice barplot with q-values above bars grouped by feature rho ~ group | feature
qvalue_feature_split <- split(round(dataset$qvalue, 2), dataset$feature)

create.barplot(
  formula = rho ~ group | feature,
  data = dataset,
  ylimits = c(-1.2, 1.2)
) + layer({
  ltext(x, y + sign(y) * 0.15, labels = qvalue_feature_split[[panel.number()]])
}, data = list(qvalue_feature_split = qvalue_feature_split))

Created on 2024-03-15 with reprex v2.1.0

Genius! Thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants