私はこの答えを、一部は後世のために、そして一部はRでカスタム視覚化を始めようとしている他の人のためにこのようないくつかの関数を書くつもりだったので書いています。
バックグラウンド
Rでは、多くの人が基本プロット関数を正しく残し、より柔軟なラッパーパッケージである「lattice」と「ggplot2」に目を向け始めます。これらは、単一のプロットの上にロジックのレイヤーを適用することにより、データを迅速に探索するための強力なツールです。次に、パッケージはすべてのレイヤーを処理し、適切に配置された1つのプロットウィンドウを生成します。これらのパッケージは素晴らしいので、すべてのRユーザーが少なくとも1つを学ぶことをお勧めします。
ただし、注意点の1つは、「lattice」および「ggplot2」パッケージは、インテリジェントなデータ視覚化よりもデータ探索に適しているということです。カスタムデータの視覚化を作成する場合、これらのパッケージはあまりにも多くの決定を下します。これは、ラッパーが目的であるためです。つまり、いくつかの決定を手に負えないようにするためです。
カスタム視覚化?「グリッド」と入力します
基本の「グリッド」パッケージは、描画の柔軟性において究極のものです。これは、基本のプロット関数をラップするのではなく、機能を拡張するためです。「グリッド」関数を使用すると、配置とサイズ設定にさまざまな異なる単位を使用してビジュアルオブジェクトを作成できるようになり、(これは非常に重要です)オブジェクトのアンカーに位置揃えを使用できるようになります。Paul Murrellの本「RGraphics」は、学びたい場合に優れたリソースです。そのコピーが私の机の上にあります。
ベクターグラフィックス描画プログラム(IllustratorやInkscapeなど)を使用したことがある場合は、正当化について言及するときに、私が何について話しているかをすでにご存知でしょう。これは、他のアイテムの場所を参照してアイテムを並べる機能です。これについてはもっと話したいのですが、一日中話すことができました。プロセスに移りましょう。
プロセス
さて、関数ライブラリを書くのに約2時間、デモコードを書くのに約5分かかったと言って、この前置きをする必要があります。今後は関数ライブラリをトレーニングツールとして利用していきますので、どなたでもお気軽にご利用・変更していただけます。
「グリッド」プロセスは、次の3つの基本ステップで機能します。
- ビューポートを作成する
- いくつかのオブジェクトを描画します
- ビューポートをポップします
ビューポートを作成する際に、「pushViewport」を使用して「viewport」オブジェクトをプッシュします。次のようになります。
pushViewport(viewport(x=0, y=1, xscale=c(1, 10), yscale=c(0, 100), width=0.25, height=0.25, default.units="npc", just=c("left","bottom"), clip="off"))
基本ビューポートには「npc」単位のセットがあり、xは0から1、左から右、yは0から1、下から上になります。これは、原点が左下隅にあることを意味します。上記のビューポートは、左下隅のプロットの4分の1として作成されます。ただし、「xscale」と「yscale」を指定すると、オブジェクトを描画するときに「ネイティブ」の単位を参照できるようになります。つまり、データの描画には「ネイティブ」単位を使用でき、軸やラベルなどの描画には「npc」単位を使用できます。
オブジェクトを描画するときは、「grid.lines」、「grid.polygon」、「grid.points」、「grid.circle」などの関数を使用します。私がこれまでに作成したすべての視覚化では、これらのオブジェクトが使用されています。これらのオブジェクトを手動で指定してデータを描画すると、非常に多くの制御が可能になります。折れ線グラフの記入は、追加機能の最も明白な例の1つです。塗りつぶされた領域は、データで指定されたポリゴンのポイントと2つのアンカーポイントが追加されたポリゴンです。これを使用して、折れ線グラフの領域を強調表示したり、同じグラフ上の複数の線を読みやすくしたりします。
また、たとえば、長方形ではないバーを作成したり、より洗練された方法で複数のプロットを組み合わせたりするなど、創造性を発揮することもできます。私と他の何人かは最近、SFをテーマにしたウォーキングゲームを実行し、カスタムチャート(「グリッド」で作成)を使用して最終的なパフォーマンスを表示しました。このグラフは、「サバイバー」チームの日数を時間軸として組み合わせ、1日あたりのプレーヤーと敵の歩数を棒グラフで表示し、1日あたりのプレーヤーと敵の累積歩数を塗りつぶしの折れ線グラフで表示します。'lattice'または'ggplot2'パッケージを使用して同等のビジュアルを作成するのは難しいでしょう。
これは、チャートの1つ(実際のプレーヤー名を除く)のサンプルであり、「グリッド」ビジュアルがどれほど柔軟であるかを示しています。
質問の概念実証
ここで、OPによって提起された質問に具体的に対処します。質問では、OPは、各エリア内にチャートをプロットすることを意味します。ほとんどのプロット関数は、すでに設定したプロット仕様を上書きするため、事前に作成されたプロットパッケージを使用する場合、これは注意が必要です。代わりに、ベースの「グリッド」関数などを使用してプロット領域を指定し、ビューポート内に必要なデータオブジェクトを描画する方が信頼性が高くなります。
働きすぎないようにするために、私は最初に、さまざまなグラフパラメーターを設定し、各タイプのグラフを描画するカスタム関数ライブラリを作成しました。私はコードをデバッグするのが好きではないので、関数は私が物事をバラバラに処理する方法です。コードを正しく取得するたびに、後で使用するためにそれを関数にスローします。
コードは少し複雑に見えるかもしれませんが、ビューポートのプッシュ、描画、ビューポートのポップという3つの「グリッド」ステップを覚えておいてください。これは、各関数が実行していることです。作業のデモを行うために、4つの異なる描画関数を作成しました。塗りつぶされた折れ線グラフ、散布図、ヒストグラム、およびOPによって提案されたボックス描画です。各関数は、各グラフのデータ値の複数のセットに対応するのに十分な柔軟性があり、補正するアルファ値を設定し、値を重ねて表示できるようにします。
このような場合、必要なだけ機能を柔軟にするので、私は行に1つのショートカットを取り、多くの仮定を行ったデモのコードからそれらを引き出しました。ただし、単純なロジックでより複雑なオブジェクトを描画する方法をデモするために、ロジック駆動型のコードで描画しました。
簡単なデータ(EuStockMarkets、nottem、sunspots.month)用の組み込みのRデータセットを使用したデモコードの結果は次のとおりです。
カスタム関数ライブラリ:
library(grid)
# Specify general chart options.
chart_Fill = "lemonchiffon"
chart_Col = "snow3"
space_Background = "white"
title_CEX = 0.8
axis_CEX = 0.6
chart_Width <- 3/3
chart_Height <- 2/5
# Function to initialize a plotting area.
init_Plot <- function(
.df,
.x_Loc,
.y_Loc,
.justify,
.width,
.height
){
# Initialize plotting area to fit data.
# We have to turn off clipping to make it
# easy to plot the labels around the plot.
pushViewport(viewport(xscale=c(min(.df[,1]), max(.df[,1])), yscale=c(min(0,min(.df[,-1])), max(.df[,-1])), x=.x_Loc, y=.y_Loc, width=.width, height=.height, just=.justify, clip="off", default.units="native"))
# Color behind text.
grid.rect(x=0, y=0, width=unit(axis_CEX, "lines"), height=1, default.units="npc", just=c("right", "bottom"), gp=gpar(fill=space_Background, col=space_Background))
grid.rect(x=0, y=1, width=1, height=unit(title_CEX, "lines"), default.units="npc", just=c("left", "bottom"), gp=gpar(fill=space_Background, col=space_Background))
# Color in the space.
grid.rect(gp=gpar(fill=chart_Fill, col=chart_Col))
}
# Function to finalize and label a plotting area.
finalize_Plot <- function(
.df,
.plot_Title
){
# Label plot using the internal reference
# system, instead of the parent window, so
# we always have perfect placement.
grid.text(.plot_Title, x=0.5, y=1.05, just=c("center","bottom"), rot=0, default.units="npc", gp=gpar(cex=title_CEX))
grid.text(paste(names(.df)[-1], collapse=" & "), x=-0.05, y=0.5, just=c("center","bottom"), rot=90, default.units="npc", gp=gpar(cex=axis_CEX))
grid.text(names(.df)[1], x=0.5, y=-0.05, just=c("center","top"), rot=0, default.units="npc", gp=gpar(cex=axis_CEX))
# Finalize plotting area.
popViewport()
}
# Function to plot a filled line chart of
# the data in a data frame. The first column
# of the data frame is assumed to be the
# plotting index, with each column being a
# set of y-data to plot. All data is assumed
# to be numeric.
plot_Line_Chart <- function(
.df,
.x_Loc,
.y_Loc,
.justify,
.width,
.height,
.colors,
.plot_Title
){
# Initialize plot.
init_Plot(.df, .x_Loc, .y_Loc, .justify, .width, .height)
# Calculate what value to use as the
# return for the polygons.
y_Axis_Min <- min(0, min(.df[,-1]))
# Plot each set of data as a polygon,
# so we can fill it in with color to
# make it easier to read.
for (i in 2:ncol(.df)){
grid.polygon(x=c(min(.df[,1]),.df[,1], max(.df[,1])), y=c(y_Axis_Min,.df[,i], y_Axis_Min), default.units="native", gp=gpar(fill=.colors[i-1], col=.colors[i-1], alpha=1/ncol(.df)))
}
# Draw plot axes.
grid.lines(x=0, y=c(0,1), default.units="npc")
grid.lines(x=c(0,1), y=0, default.units="npc")
# Finalize plot.
finalize_Plot(.df, .plot_Title)
}
# Function to plot a scatterplot of
# the data in a data frame. The
# assumptions are the same as 'plot_Line_Chart'.
plot_Scatterplot <- function(
.df,
.x_Loc,
.y_Loc,
.justify,
.width,
.height,
.colors,
.plot_Title
){
# Initialize plot.
init_Plot(.df, .x_Loc, .y_Loc, .justify, .width, .height)
# Plot each set of data as colored points.
for (i in 2:ncol(.df)){
grid.points(x=.df[,1], y=.df[,i], pch=19, size=unit(1, "native"), default.units="native", gp=gpar(col=.colors[i-1], alpha=1/ncol(.df)))
}
# Draw plot axes.
grid.lines(x=0, y=c(0,1), default.units="npc")
grid.lines(x=c(0,1), y=0, default.units="npc")
# Finalize plot.
finalize_Plot(.df, .plot_Title)
}
# Function to plot a histogram of
# all the columns in a data frame,
# except the first, which is assumed to
# be an index.
plot_Histogram <- function(
.df,
.x_Loc,
.y_Loc,
.justify,
.width,
.height,
.colors,
.plot_Title,
...
){
# Create a list containing the histogram
# data for each data column and calculate
# data ranges. Any extra parameters
# specified will pass to the 'hist' function.
hist_Data <- list()
hist_Count_Range <- c(0,NA)
hist_Breaks_Range <- c(NA,NA)
for (i in 2:ncol(.df)){
hist_Data[[i]] <- hist(.df[,i], plot=FALSE, ...)
hist_Count_Range[2] <- max(max(hist_Data[[i]]$counts), hist_Count_Range[2], na.rm=TRUE)
hist_Breaks_Range <- c(min(min(hist_Data[[i]]$breaks), hist_Breaks_Range[1], na.rm=TRUE), max(max(hist_Data[[i]]$breaks), hist_Breaks_Range[2], na.rm=TRUE))
}
# Initialize plotting area to fit data.
# We are doing this in a custom way to
# allow more flexibility than built into
# the 'init_Plot' function.
# We have to turn off clipping to make it
# easy to plot the labels around the plot.
pushViewport(viewport(xscale=hist_Breaks_Range, yscale=hist_Count_Range, x=.x_Loc, y=.y_Loc, width=.width, height=.height, just=.justify, clip="off", default.units="native"))
# Color behind text.
grid.rect(x=0, y=0, width=unit(axis_CEX, "lines"), height=1, default.units="npc", just=c("right", "bottom"), gp=gpar(fill=space_Background, col=space_Background))
grid.rect(x=0, y=1, width=1, height=unit(title_CEX, "lines"), default.units="npc", just=c("left", "bottom"), gp=gpar(fill=space_Background, col=space_Background))
# Color in the space.
grid.rect(gp=gpar(fill=chart_Fill, col=chart_Col))
# Draw x axis.
grid.lines(x=c(0,1), y=0, default.units="npc")
# Plot each set of data as a histogram.
for (i in 2:ncol(.df)){
grid.rect(x=hist_Data[[i]]$mids, y=0, width=diff(hist_Data[[i]]$mids[1:2]), height=hist_Data[[i]]$counts, default.units="native", just=c("center","bottom"), gp=gpar(fill=.colors[i-1], col=.colors[i-1], alpha=1/ncol(.df)))
}
# Label plot using the internal reference
# system, instead of the parent window, so
# we always have perfect placement.
grid.text(.plot_Title, x=0.5, y=1.05, just=c("center","bottom"), rot=0, default.units="npc", gp=gpar(cex=title_CEX))
grid.text(paste(names(.df)[-1], collapse=" & "), x=-0.05, y=0.5, just=c("center","bottom"), rot=90, default.units="npc", gp=gpar(cex=axis_CEX))
# Finalize plotting area.
popViewport()
}
draw_Sample_Box <- function(
.x_Loc,
.y_Loc,
.x_Scale,
.y_Scale,
.justify,
.width,
.height,
.colors,
.box_X,
.box_Y,
.plot_Title
){
pushViewport(viewport(xscale=.x_Scale, yscale=.y_Scale, x=.x_Loc, y=.y_Loc, width=chart_Width, height=chart_Height, just=.justify, clip="off", default.units="native"))
# Color behind text.
grid.rect(x=0, y=1, width=1, height=unit(title_CEX, "lines"), default.units="npc", just=c("left", "bottom"), gp=gpar(fill=space_Background, col=space_Background))
# Color in the space.
grid.rect(gp=gpar(fill=chart_Fill, col=chart_Col))
# Label plot.
grid.text(.plot_Title, x=0.5, y=1.05, just=c("center","bottom"), rot=0, default.units="npc", gp=gpar(cex=title_CEX))
# Draw box and label points.
grid.polygon(x=.box_X, y=.box_Y, default.units="native", gp=gpar(fill=.colors[1], col=.colors[2]))
grid.text(paste(.plot_Title, 1, sep=""), x=min(.box_X), y=min(.box_Y), default.units="native", just=c("right","top"), gp=gpar(cex=0.5))
grid.text(paste(.plot_Title, 2, sep=""), x=max(.box_X), y=min(.box_Y), default.units="native", just=c("left","top"), gp=gpar(cex=0.5))
# Finalize plot.
popViewport()
}
デモコード:
# Draw twelve independent charts as
# a demo and connect with lines similar
# to a heiritage chart.
grid.newpage()
# Initialize a viewport to make our locations
# easier to map.
pushViewport(viewport(x=0, y=0, width=1, height=1, just=c("left","bottom"), xscale=c(0,10), yscale=c(0,4)))
# Color background of overall plot.
grid.rect(gp=gpar(fill=space_Background, col=space_Background))
# Store plot locations for convenience.
plot_Loc <- data.frame(x=c(2,4,6,8,1,3,7,9,2,4,6,8), y=c(3,3,3,3,2,2,2,2,1,1,1,1))
# Draw connecting lines.
connections <- data.frame(a=c(1, 3, 5, 6, 7, 1, 3, 5, 7, 6), b=c(2, 4, 6, 7, 8, 2, 4, 6, 8, 7), c=c(NA, NA, NA, NA, NA, 6, 7, 9, 12, 10), d=c(NA, NA, NA, NA, NA, NA, NA, NA, NA, 11))
for (i in 1:nrow(connections)){
if (is.na(connections$c[i])){
grid.lines(x=plot_Loc$x[unlist(connections[i,1:2])], y=plot_Loc$y[unlist(connections[i,1:2])], default.units="native")
} else if (is.na(connections$d[i])) {
grid.lines(x=median(plot_Loc$x[unlist(connections[i,1:2])]), y=plot_Loc$y[unlist(connections[i,2:3])], default.units="native")
} else {
grid.lines(x=median(plot_Loc$x[unlist(connections[i,1:2])]), y=c(plot_Loc$y[connections[i,2]], median(plot_Loc$y[unlist(connections[i,2:3])])), default.units="native")
grid.lines(x=plot_Loc$x[unlist(connections[i,3:4])], y=median(plot_Loc$y[unlist(connections[i,2:3])]), default.units="native")
grid.lines(x=plot_Loc$x[connections[i,3]], y=c(median(plot_Loc$y[unlist(connections[i,2:3])]), plot_Loc$y[connections[i,3]]), default.units="native")
grid.lines(x=plot_Loc$x[connections[i,4]], y=c(median(plot_Loc$y[unlist(connections[i,2:3])]), plot_Loc$y[connections[i,4]]), default.units="native")
}
}
# Draw four independent line charts.
p <- 1
plot_Line_Chart(data.frame(time=1:1860, EuStockMarkets)[1:3], .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("dodgerblue", "deeppink"), "EU Stocks")
p <- 2
plot_Line_Chart(data.frame(time=1:1860, EuStockMarkets)[c(1,4,5)], .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("green", "purple"), "EU Stocks")
p <- 3
plot_Line_Chart(data.frame(time=1:(12*20), sunspots=sunspot.month[(171*12+1):(171*12+12*20)]), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("darkgoldenrod"), "Sunspots")
p <- 4
plot_Line_Chart(data.frame(time=1:(12*20), temp=nottem), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("red"), "Nottem")
# Draw four independent scatterplots.
p <- 5
plot_Scatterplot(data.frame(time=1:(1860 + 1 - 1000), DAX=rowMeans(embed(EuStockMarkets[,1], 1000)), FTSE=rowMeans(embed(EuStockMarkets[,4], 1000))), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("deeppink", "purple"), "Smooth")
p <- 6
plot_Scatterplot(data.frame(time=1:1860, EuStockMarkets)[c(1,2,5)], .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("deeppink", "purple"), "EU Stocks")
p <- 9
plot_Scatterplot(data.frame(time=1:(1860 + 1 - 20), DAX=rowMeans(embed(EuStockMarkets[,1], 20)), FTSE=rowMeans(embed(EuStockMarkets[,4], 20))), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("deeppink", "purple"), "Smooth*20")
p <- 10
plot_Scatterplot(data.frame(time=1:(1860 + 1 - 100), DAX=rowMeans(embed(EuStockMarkets[,1], 100)), FTSE=rowMeans(embed(EuStockMarkets[,4], 100))), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("deeppink", "purple"), "Smooth*100")
# Draw two independent histograms.
p <- 7
plot_Histogram(data.frame(time=1:(12*20), sunspots=sunspot.month[(171*12+1):(171*12+12*20)]), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("darkgoldenrod"), "Sunspots", breaks=6)
p <- 8
plot_Histogram(data.frame(time=1:(12*20), temp=nottem), .x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .just=c("center","center"), .width=chart_Width, .height=chart_Height, c("red"), "Nottem", breaks=6)
# Draw sample objects in two charts spaces.
p <- 11
draw_Sample_Box(.x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .x_Scale=c(0,10), .y_Scale=c(-10,0), .justify=c("center","center"), .width=chart_Width, .height=chart_Height, .colors=c("dodgerblue","blue"), .box_X=c(4,6,6,4), .box_Y=c(-4,-4,-5,-5), .plot_Title="K")
p <- 12
draw_Sample_Box(.x_Loc=plot_Loc$x[p], .y_Loc=plot_Loc$y[p], .x_Scale=c(-1,1), .y_Scale=c(0,1), .justify=c("center","center"), .width=chart_Width, .height=chart_Height, .colors=c("dodgerblue","blue"), .box_X=c(-0.5,0,0,-0.5), .box_Y=c(0.8,0.8,0.7,0.7), .plot_Title="L")