JVGen
JVGen

Reputation: 601

R: Create defined space for Y-Axis Labels and remove decimals

I have a code that makes a ggplot and scales the y-axis limits based on the range of the data. The Y-axis labels have a different number of digits, causing the plots to shift slightly to the right (the difference is slight, but present). This creates issues when viewing the plots together, because the x-axis across the plots are offset. I cannot facet these plots because the data for each plot originates from large, independent datasets that are analyzed sequentially.

  1. Is there a way to create "white space" in the Y-axis label area, so that regardless of the number of digits displayed the x-axis will always occupy the same area within the figure?

  2. How do I control the number of decimal places in my plot? I only want whole numbers as axis labels.

I saw a post that addresses #2, but the responses rely on using scale_y_continuous(). I adjusted my code to use scale_y_continuous() instead of ylim(), but the decimal places persist (see Plotv2.1).

I have provided a simplified example, but I think it captures the issue. Please keep in mind that the plots must be exported independently so faceting or using ggarrange to align axes is not possible.

Thank you!

library(ggplot2)
library(grid)

df <- data.frame(
  Position=(c(1:5000)),
  Signal=(c((rep(c(-1),times=2000)), (rep(c(100),times=1000)), (rep(c(-1),times=2000))))
)

Plotv1 <- ggplot() +
  geom_line(data = df, aes(x=Position, y=Signal, col = "#000000")) +
  ylim(-ceiling(max(abs(df$Signal))),ceiling(max(abs(df$Signal)))) +
  coord_cartesian(clip="off") +
  guides(fill = "none") +
  guides(color=guide_legend(title="MyLegend")) +
  theme_bw() +
  theme(axis.text.x = element_blank()) +
  theme(
    axis.title.x = element_text(margin=margin(t=30)),
    legend.title = element_text(colour = "#000000", size=12),
    legend.text = element_text(colour = "#000000", size=12)
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-25, "pt"),
      y1 = unit(-25, "pt"),
      arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
      gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
    ),
    xmin = 2000,
    xmax = 3000
  )

ggsave(paste("~/Desktop/Plotv1.png", sep = ""), Plotv1, width = 8, height = 1.7)

df2 <- data.frame(
  Position=(c(1:5000)),
  Signal=(c((rep(c(-1),times=2000)), (rep(c(4.9),times=1000)), (rep(c(-1),times=2000))))
)


Plotv2 <- ggplot() +
  geom_line(data = df2, aes(x=Position, y=Signal, col = "#000000")) +
  ylim(-ceiling(max(abs(df2$Signal))),ceiling(max(abs(df2$Signal)))) +
  coord_cartesian(clip="off") +
  guides(fill = "none") +
  guides(color=guide_legend(title="MyLegend")) +
  theme_bw() +
  theme(axis.text.x = element_blank()) +
  theme(
    axis.title.x = element_text(margin=margin(t=30)),
    legend.title = element_text(colour = "#000000", size=12),
    legend.text = element_text(colour = "#000000", size=12)
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-25, "pt"),
      y1 = unit(-25, "pt"),
      arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
      gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
    ),
    xmin = 2000,
    xmax = 3000
  )

ggsave(paste("~/Desktop/Plotv2.png", sep = ""), Plotv2, width = 8, height = 1.7)



Plotv2.1 <- ggplot() +
  geom_line(data = df2, aes(x=Position, y=Signal, col = "#000000")) +
  scale_y_continuous(labels = function(x) format(x, nsmall = 0),
                     limits = c(-ceiling(max(abs(df2$Signal))),ceiling(max(abs(df2$Signal))))) +
  coord_cartesian(clip="off") +
  guides(fill = "none") +
  guides(color=guide_legend(title="MyLegend")) +
  theme_bw() +
  theme(axis.text.x = element_blank()) +
  theme(
    axis.title.x = element_text(margin=margin(t=30)),
    legend.title = element_text(colour = "#000000", size=12),
    legend.text = element_text(colour = "#000000", size=12)
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-25, "pt"),
      y1 = unit(-25, "pt"),
      arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
      gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
    ),
    xmin = 2000,
    xmax = 3000
  )

ggsave(paste("~/Desktop/Plotv2.1.png", sep = ""), Plotv2.1, width = 8, height = 1.7)

Plotv1 Plotv2 Plotv2.1

Upvotes: 1

Views: 193

Answers (3)

CPB
CPB

Reputation: 756

Can you use the approach in https://stackoverflow.com/a/69843619/4413615 to get this working in ggplot2? Use a formatting function on the axis labels through scale_y_continuous(labels = \(x) formatC(x, width = 6)) and change the font to fixed-width using theme(axis.text = element_text(family = "mono")). The width of 6 can be changed to the maximum width observed across your plots.

Full example on Plotv1 below

library(ggplot2)
library(grid)

df <- data.frame(
  Position=(c(1:5000)),
  Signal=(c((rep(c(-1),times=2000)), (rep(c(100),times=1000)), (rep(c(-1),times=2000))))
)

Plotv1 <- ggplot() +
  geom_line(data = df, aes(x=Position, y=Signal, col = "#000000")) +
  scale_y_continuous(labels = \(x) formatC(x, width = 6),
                     limits = c(-ceiling(max(abs(df$Signal))),ceiling(max(abs(df$Signal))))) +
  coord_cartesian(clip="off") +
  guides(fill = "none") +
  guides(color=guide_legend(title="MyLegend")) +
  theme_bw() +
  theme(axis.text.x = element_blank()) +
  theme(
    axis.title.x = element_text(margin=margin(t=30)),
    axis.text = element_text(family = "mono"),
    legend.title = element_text(colour = "#000000", size=12),
    legend.text = element_text(colour = "#000000", size=12)
  ) +
  annotation_custom(
    grid::segmentsGrob(
      y0 = unit(-25, "pt"),
      y1 = unit(-25, "pt"),
      arrow = arrow(angle=45, type="closed", length=unit(.15, 'cm')),
      gp = grid::gpar(lwd=3, col="#000000", fill="#000000")
    ),
    xmin = 2000,
    xmax = 3000
  )

Upvotes: 1

jay.sf
jay.sf

Reputation: 73712

If you want to consider using base graphics, their labels and axis positions might be more consistent, even with decimal places. We will use normalized plot coordinates (npc) in grconvertY to get consistent arrow and legend positions. Assuming your data is in a list "dfl" you could loop over, and the legend texts are in another list "leg", you could proceed as follows.

for (i in seq_along(dfl)) {
  png(sprintf('plot%s.png', i), 800, 200)  ## open png device
  par(mar=c(4, 4, 1, 9) + .1)  ## set margins
  plot(dfl[[i]], type='l', col='red', xaxt='n', xlab='Position', ylab='Signal',
       panel.first=grid(), las=1, ylim=max(dfl[[i]]$Signal)*c(-1, 1))
  axis(1, axTicks(1), labels=FALSE) 
  y <- grconvertY(c(-.25, .6), from='npc', to='user')  ## get y for arrow and leg
  arrows(x0=2000, y0=y[1], x1=3000, y1=y[1], lwd=2, xpd=TRUE, length=.05)
  legend(par()$usr[2]*1.03, y[2], title=leg[[i]][1], leg=leg[[i]][2], lty=1, 
         col='red', xpd=TRUE, bty='n')
  dev.off()  ## close png, which saves plot
}

enter image description here enter image description here enter image description here

You could also consider using pdf() instead of png() to get vector graphics.


Data:

n <- 5e3
dfl <- lapply(c(100, 5, 1), \(s) data.frame(
  Position=seq_len(n),
  Signal=replace(rep_len(-1, n), 2001:3e3, s)
))
leg <- list(c("MyLegend 1", "#000000 1"), c("MyLegend 2", "#000000 2"),
            c("MyLegend 3", "#000000 3"))

Upvotes: 1

TarJae
TarJae

Reputation: 79266

  1. With theme(axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0))) we can tweak the position on the right side of the plot by tweaking r = 10 etc..
  2. With scale_y_continuous(labels = label_number(accuracy = 1)) from scales package we can control thy y axis numbers.

I use ggarrange to demonstrate how the plots look when plotted in one column. If you change r in element_text the alignment will change:

library(ggpubr)
library(scales)
Plotv1 <- Plotv1 +
  theme(axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)))+
  #scale_y_continuous(labels = label_number(accuracy = 0.01))
  scale_y_continuous(labels = label_number(accuracy = 1))

Plotv2 <- Plotv2 +
  theme(axis.title.y = element_text(margin = margin(t = 0, r = 16, b = 0, l = 0)))+
  #scale_y_continuous(labels = label_number(accuracy = 0.01))
  scale_y_continuous(labels = label_number(accuracy = 1))

Plotv2.1 <- Plotv2.1 +
  theme(axis.title.y = element_text(margin = margin(t = 0, r = 16, b = 0, l = 0)))+
  #scale_y_continuous(labels = label_number(accuracy = 0.01))
  scale_y_continuous(labels = label_number(accuracy = 1))
ggarrange(Plotv1, Plotv2, Plotv2.1, ncol = 1)

enter image description here

Upvotes: 1

Related Questions