web-dev-qa-db-ja.com

ggplot2:geom_textのサイズをプロットで変更し、geom_bar内のテキストを強制/調整します

これは実際には1つに2つの質問です(SOルールに反するかどうかはわかりませんが、とにかく)。

最初の質問は、どうすればgeom_textgeom_bar内に収めることができるかということです。 (プロットされた値に従って動的に)

周りを見回すと、私が見つけた解決策はラベルのサイズを変更することでした。それは確かに機能しますが、すべての場合に当てはまるわけではありません。特定のプロットのサイズを変更して、テキストをバー内に収めることができますが、データが変更された場合は、テキストのサイズを手動で再度変更する必要がある場合があります。私の実際の問題は、絶えず変化するデータ(毎日)に対して同じプロットを生成する必要があるため、各プロットのサイズを手動で調整できないことです。

ラベルのサイズをデータの関数として設定してみました。完全ではありませんが、多くの場合に機能します。

しかし、ラベルがバー内に収まっている場合でも、プロットのサイズを変更するとすべてが台無しになるという別の問題があります。それを調べてみると、 ggplotドキュメント も見つかりました

ラベルには高さと幅がありますが、データ単位ではなく物理単位です。そのプロット上でそれらが占めるスペースの量は、データ単位で一定ではありません。プロットのサイズを変更すると、ラベルは同じサイズのままですが、軸のサイズが変わります。

これは私の2番目の質問に私を連れて行きます:このデフォルトの動作を変更して、プロットでラベルのサイズを変更することは可能ですか?

また、最初の質問を絞り込みます。物理単位とデータ単位の間の巧妙な関係を使用してテキストのサイズを動的に設定し、geom_textgeom_bar内に強制的に収めることは可能ですか?

だから、良い習慣に従うために、これが私の再現可能な例です:

set.seed(1234567)
data_Gd <- data.frame(x = letters[1:5], 
                      y = runif(5, 100, 99999))

ggplot(data = data_Gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2))

このコードはこのプロットを生成します:

enter image description here

プロットのサイズを単純に変更すると、 " ラベルは同じサイズのままですが、軸のサイズが変わります "これにより、ラベルがバーに収まります(ラベルが小さすぎる可能性があります)。

enter image description here

それで、これは私の2番目の質問です。ラベルのサイズも変更され、バーとの関係でアスペクト比が維持されると便利です。これを達成する方法、またはそれが可能かどうかについてのアイデアはありますか?

わかりましたが、バー内にラベルを合わせる方法に戻ると、最も簡単な解決策はラベルのサイズを設定することです。

ggplot(data = data_Gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2), size = 3)

繰り返しになりますが、これは以下に示すように機能しますが、データの変更に対して保守可能/堅牢ではありません。

enter image description here

たとえば、異なるデータでプロットを生成するためのまったく同じコードは、壊滅的な結果をもたらします。

data_Gd <- data.frame(x = letters[1:30], 
                      y = runif(30, 100, 99999))
ggplot(data = data_Gd,
       mapping = aes(x = x, y = y, fill = x)) +
    geom_bar(stat = "identity") +
    geom_text(mapping = aes(label = y, y = y/2), size = 3)

enter image description here

そして、x軸のカテゴリ数などの関数としてラベルのサイズを設定して、例を続けることができます。しかし、あなたは要点を理解し、おそらくあなたのggplot2専門家の1人が私にアイデアを与えることができます。

17

1つのオプションは、バーの幅で設定された割り当てられたスペース内に収まるように、カスタムのdrawDetailsメソッドでtextGrobを使用するgeomを作成することです。

library(grid)
library(ggplot2)

fitGrob <- function(label, x=0.5, y=0.5, width=1){
  grob(x=x, y=y, width=width, label=label, cl = "fit")
}
drawDetails.fit <- function(x, recording=FALSE){
  tw <- sapply(x$label, function(l) convertWidth(grobWidth(textGrob(l)), "native", valueOnly = TRUE))
  cex <- x$width / tw
  grid.text(x$label, x$x, x$y, gp=gpar(cex=cex), default.units = "native")
}


`%||%` <- ggplot2:::`%||%`

GeomFit <- ggproto("GeomFit", GeomRect,
                   required_aes = c("x", "label"),

                   setup_data = function(data, params) {
                     data$width <- data$width %||%
                       params$width %||% (resolution(data$x, FALSE) * 0.9)
                     transform(data,
                               ymin = pmin(y, 0), ymax = pmax(y, 0),
                               xmin = x - width / 2, xmax = x + width / 2, width = NULL
                     )
                   },
                   draw_panel = function(self, data, panel_scales, coord, width = NULL) {
                     bars <- ggproto_parent(GeomRect, self)$draw_panel(data, panel_scales, coord)
                     coords <- coord$transform(data, panel_scales)    
                     width <- abs(coords$xmax - coords$xmin)
                     tg <- fitGrob(label=coords$label, y = coords$y/2, x = coords$x, width = width)

                     grobTree(bars, tg)
                   }
)

geom_fit <- function(mapping = NULL, data = NULL,
                     stat = "count", position = "stack",
                     ...,
                     width = NULL,
                     binwidth = NULL,
                     na.rm = FALSE,
                     show.legend = NA,
                     inherit.aes = TRUE) {

  layer(
    data = data,
    mapping = mapping,
    stat = stat,
    geom = GeomFit,
    position = position,
    show.legend = show.legend,
    inherit.aes = inherit.aes,
    params = list(
      width = width,
      na.rm = na.rm,
      ...
    )
  )
}


set.seed(1234567)
data_Gd <- data.frame(x = letters[1:5], 
                      y = runif(5, 100, 99999))

ggplot(data = data_Gd,
       mapping = aes(x = x, y = y, fill = x, label=round(y))) +
  geom_fit(stat = "identity") +
  theme()

enter image description here

11
baptiste

横棒グラフで問題がない場合、問題はラベルのサイズではなく配置です。私の解決策は

enter image description here

このコードによって作成されました:

_library(ggplot2)
data_Gd <- data.frame(x = letters[1:26], 
                      y = runif(26, 100, 99999))
ymid <- mean(range(data_Gd$y))
ggplot(data = data_Gd,
       mapping = aes(x = x, y = y, fill = x)) +
  geom_bar(stat = "identity") +
  geom_text(mapping = aes(label = y, y = y, 
            hjust = ifelse(y < ymid, -0.1, 1.1)), size = 3) +
  coord_flip()
_

トリックは3つのステップで行われます:

  1. _coord_flip_は横棒グラフを作成します。
  2. _geom_text_のマッピングも、yの値に応じてhjustを使用します。バーがyの範囲の半分より短い場合、テキストはバーの外側(y値の右側)に印刷されます。バーがyの範囲の半分より長い場合、テキストはバーの内側に印刷されます(y値の左側)。これにより、テキストが常にプロット領域内に印刷されます(長すぎない場合)。
  3. バーとテキストの間にスペースを追加しました。テキストをy値で直接開始または終了する場合は、hjust = ifelse(y < ymid, 0, 1))を使用できます。
6
Uwe