Rで出力したグラフにlegend()関数を使って凡例を書き込んだところ、凡例のボックスの幅が広がって文字列の右側に余白ができてしまう現象にハマった。そのワークアラウンドです。
実行環境
R 4.3.0, macOS 12.6.5
サンプルのコードはこれです。今回プロット領域の外側にも凡例を書きたいので、1行目のpar関数で引数marを指定し、プロット右側のマージンを8に設定しました1。さらにxpd=TRUEを指定して凡例のボックスがプロット領域でクリップされないようにします。
op <- par(mar=c(5.1,4.1,4.1,8), xpd=T)
items <- c("sample A 12", "sample B")
colors <- c("red", "blue")
boxplot(15:75,30:90,5:35,2:30)
legend("topleft", inset=c(1.01,0), legend=items, fill=colors)
legend("topright", legend=items, fill=colors)
legend("left", inset=c(1.01,0), legend=items, fill=colors)
legend("right", legend=items, fill=colors)
legend("topleft", legend=items, fill=colors)
legend("bottomleft", legend=c("sample A 12345", "sample B"), fill=colors)
par(op)
このスニペットをRのコンソールにコピペして実行すると、

凡例のボックスの幅が項目の長さに応じて自動的に調整されました。これは期待どおりの結果です。

次に、上記のスニペットをファイルに保存し、コンソールからsource()関数でこのスクリプトを読み込ませて実行してみましょう。

結果は下図のとおり。凡例のボックスの幅が広がって文字列の右側に大きな余白ができています。さらに、プロット領域の外側に配置した凡例はウインドウの右端でクリップされ、ボックスの右側が欠けてしまいました。

なぜsource()で実行した場合は凡例のサイズが正しく調整されないのでしょうか?コンソールにコードを直接入力した場合とsource()とで、何か実行のタイミングの違いが影響してそうです。そこで、legend()の呼び出しのタイミングを遅らせて試したところ、問題は解消しました!以下、その方法です。
方法1. プロンプトを表示して処理を一時停止する
最初に試した方法。Rがグラフをプロットする前にプロンプトを表示して処理を一時停止し、Returnキーを押すとグラフが表示されるようにしてみます。プロンプトを表示するには、1行目のpar()の引数にask=TRUEを追加するだけです。
op <- par(mar=c(5.1,4.1,4.1,8), xpd=T, ask=T)
このように修正したスクリプトをsource()から実行してみます。

最初ブランクのQuartzウインドウが表示されますが、Returnキーを押すとグラフが表示されます。結果、凡例のボックスは期待どおりのサイズで表示され、コンソールにコードを打ち込んだ場合と同じになりました。

方法2. legend()の前にSys.sleep()を入れる
グラフをプロットした後、最初のlegend()を呼び出す前にSys.sleep()関数を呼んでRの処理を一時停止します。サンプルのコードでは、boxplot()の直後にSys.sleep()を入れます。
boxplot(15:75,30:90,5:35,2:30)
Sys.sleep(0.5)
legend("topleft", inset=c(1.01,0), legend=items, fill=colors)
この方法でも正しく表示できました(図は省略)。ただし、Sys.sleep()の引数に指定する秒数が短すぎると効果がありません。今回試した環境では、0.1秒では効果がなく、0.2秒以上で正しく表示できました。
参考
- Legend box width not correct when using par
- How to Draw a Legend Outside of a Plot in R
- [R] Time Delay / Wait: https://stat.ethz.ch/pipermail/r-help/2010-November/259566.html
-
マージンの指定順序は、bottom, left, top, right。 ↩