折れ線グラフの線が多すぎますが、より良い解決策はありますか?


30

ユーザーによるアクションの数(この場合は「いいね」)の経時的なグラフを作成しようとしています。

したがって、Y軸として「アクションの数」、X軸は時間(週)、各行は1人のユーザーを表します。

私の問題は、約100人のユーザーのセットについてこのデータを調べたいということです。折れ線グラフは、すぐに100本の線でごちゃごちゃになります。この情報を表示するために使用できるより良いタイプのグラフはありますか?または、個々の行のオン/オフを切り替えられるようにする必要がありますか?

すべてのデータを一度に見たいのですが、アクションの数を高精度で識別できることはそれほど重要ではありません。

なぜ私はこれをしているのですか

私のユーザーのサブセット(トップユーザー)について、特定の日付にロールアウトされたアプリケーションの新しいバージョンが気に入らないユーザーを見つけたいと思います。個々のユーザーによるアクション数の大幅な減少を探しています。


5
プロットに使用されているアルファを変更して、線を半透明にすることを検討しましたか?
フォマイト

1
@EpiGrad合理的な提案ですが、それは私が探しているものを見るのを本当に簡単にするものではありません。
規制する

1
@regulatethis ggplot2のfacet_wrap機能を使用して4 x 5チャート(4行、5列-目的のアスペクト比に応じて調整)のブロックを作成し、チャートごとに最大5ユーザーの「小さな倍数」アプローチを提案します。これは十分に明確であり、チャートごとに約10ユーザーまで拡大でき、4x5プロットでは200個、6x6プロットでは360個のスペースを確保できます。
SlowLearner

回答:


31

(a)ユーザー間の変動、(b)変更に対するすべてのユーザー間の典型的な反応、および(c)ある期間から次の期間への典型的な変動の主な影響を除去するための(標準)予備分析を提案したい。

これを行うための簡単な(しかし最善の方法ではありません)方法は、データに対して「中央値ポリッシュ」を数回繰り返して、ユーザーの中央値と期間の中央値を掃引し、時間の経過とともに残差を平滑化することです。大きく変化するスムースを特定します。彼らは、グラフィックで強調したいユーザーです。

これらはカウントデータであるため、平方根を使用して再表現することをお勧めします。

結果の例として、通常は週に10〜20のアクションを実行する240人のユーザーの60週間のデータセットをシミュレートします。すべてのユーザーの変更は、40週以降に発生しました。これらの3つは、変更に否定的に応答するように「言われました」。左側のプロットは、生データを示しています。ユーザーによるアクションの数(ユーザーは色で区別されます)。質問で主張したように、それは混乱です。右側のプロットは、このEDAの結果を(以前と同じ色で)表示し、異常に反応するユーザーを自動的に識別して強調表示します。識別は(多少アドホックですが)完全で正確です(この例では)。

図1

Rこれらのデータを生成し、分析を実行したコードを次に示します。以下を含むいくつかの方法で改善できます。

  • 1回の反復ではなく、完全な中央値ポリッシュを使用して残差を見つけます。

  • 変化点の前後で残差を個別に平滑化します。

  • おそらく、より洗練された異常値検出アルゴリズムを使用します。現在のものは、残差の範囲が中央値範囲の2倍を超えるすべてのユーザーにフラグを立てているだけです。シンプルではありますが、堅牢であり、うまく機能するようです。(ユーザーが設定可能な値をthreshold調整して、この識別を多少厳密にすることができます。)

それでも、テストでは、このソリューションが12〜240人以上の幅広いユーザー数に対して適切に機能することが示唆されています。

n.users <- 240        # Number of users (here limited to 657, the number of colors)
n.periods <- 60       # Number of time periods
i.break <- 40         # Period after which change occurs
n.outliers <- 3       # Number of greatly changed users
window <- 1/5         # Temporal smoothing window, fraction of total period
response.all <- 1.1   # Overall response to the change
threshold <- 2        # Outlier detection threshold

# Create a simulated dataset
set.seed(17)
base <- exp(rnorm(n.users, log(10), 1/2))
response <- c(rbeta(n.users - n.outliers, 9, 1),
              rbeta(n.outliers, 5, 45)) * response.all
actual <- cbind(base %o% rep(1, i.break), 
                base * response %o% rep(response.all, n.periods-i.break))
observed <- matrix(rpois(n.users * n.periods, actual), nrow=n.users)

# ---------------------------- The analysis begins here ----------------------------#
# Plot the raw data as lines
set.seed(17)
colors = sample(colors(), n.users) # (Use a different method when n.users > 657)
par(mfrow=c(1,2))
plot(c(1,n.periods), c(min(observed), max(observed)), type="n",
     xlab="Time period", ylab="Number of actions", main="Raw data")
i <- 0
apply(observed, 1, function(a) {i <<- i+1; lines(a, col=colors[i])})
abline(v = i.break, col="Gray")  # Mark the last period before a change

# Analyze the data by time period and user by sweeping out medians and smoothing
x <- sqrt(observed + 1/6)                        # Re-express the counts
mean.per.period <- apply(x, 2, median)
residuals <- sweep(x, 2, mean.per.period)
mean.per.user <- apply(residuals, 1, median)
residuals <- sweep(residuals, 1, mean.per.user)

smooth <- apply(residuals, 1, lowess, f=window)  # Smooth the residuals
smooth.y <- sapply(smooth, function(s) s$y)      # Extract the smoothed values
ends <- ceiling(window * n.periods / 4)          # Prepare to drop near-end values
range <- apply(smooth.y[-(1:ends), ], 2, function(x) max(x) - min(x))

# Mark the apparent outlying users
thick <- rep(1, n.users)
thick[outliers <- which(range >= threshold * median(range))] <- 3
type <- ifelse(thick==1, 3, 1)

cat(outliers) # Print the outlier identifiers (ideally, the last `n.outliers`)

# Plot the residuals
plot(c(1,n.periods), c(min(smooth.y), max(smooth.y)), type="n",
     xlab="Time period", ylab="Smoothed residual root", main="Residuals")
i <- 0
tmp <- lapply(smooth, 
       function(a) {i <<- i+1; lines(a, lwd=thick[i], lty=type[i], col=colors[i])})
abline(v = i.break, col="Gray")

3
100〜200人を超えるユーザーの場合、誤検出を防ぐためthresholdに約に増やします。 例えば、とのコードを試してみてください、(それが大部分だ!)、および。2.5n.users <- 500n.outliers <- 100threshold <- 2.5
whuber

16

一般に、プロットの1つのファセットで2行または3行を超える行を読むのは困難になります(ただし、常にそれを行います)。したがって、これは、概念的に100ファセットプロットになる可能性のあるものがある場合の対処方法の興味深い例です。可能な方法の1つは、100個すべてのファセットを描画することですが、それらを一度にすべてページに表示しようとする代わりに、アニメーションで一度に1つずつ表示します。

私は実際に仕事でこの手法を使用しました-元々、イベント(新しいデータシリーズの立ち上げ)の背景として60の異なるラインプロットを表示するアニメーションを作成しました。 1ページあたり15または30のファセットがあるファセットプロットでは表示されませんでした。

そこで、@ whuberが推奨するように、ユーザーと通常の時間効果の削除を開始する前に、生データを表示する別の方法を示します。これは、生データの表示に対する追加の代替手段としてのみ表示されます-彼が提案するような行に沿って分析を進めることを完全にお勧めします。

この問題を回避する1つの方法は、100(@whuberの例では240)の時系列プロットを個別に作成し、それらを一緒にアニメーションにまとめることです。以下のコードは、この種の240の個別の画像を生成します。その後、無料のムービー作成ソフトウェアを使用して、それをムービーに変換できます。残念ながら、これを実行して許容可能な品質を維持できる唯一の方法は9MBのファイルでしたが、インターネット経由で送信する必要がない場合は問題ない可能性があり、とにかくもう少し方法があると確信していますアニメーションに精通。ここでは、Rのアニメーションパッケージが便利かもしれません(Rからの呼び出しですべてを行うことができます)が、この図では簡単にしています。

各ラインを濃い黒で描画し、薄い半透明の緑の影を背後に残すようにアニメーションを作成しました。これにより、蓄積データの目が徐々に見えます。これにはリスクと機会の両方があります-行が追加される順序は異なる印象を残すので、何らかの方法でそれを意味のあるものにすることを検討する必要があります。

以下は、@ whuberが生成したのと同じデータを使用する映画の静止画の一部です。 ここに画像の説明を入力してください ここに画像の説明を入力してください ここに画像の説明を入力してください ここに画像の説明を入力してください ここに画像の説明を入力してください

# ---------------------------- Data generation - by @whuber ----------------------------#

n.users <- 240        # Number of users (here limited to 657, the number of colors)
n.periods <- 60       # Number of time periods
i.break <- 40         # Period after which change occurs
n.outliers <- 3       # Number of greatly changed users
window <- 1/5         # Temporal smoothing window, fraction of total period
response.all <- 1.1   # Overall response to the change
threshold <- 2        # Outlier detection threshold

# Create a simulated dataset
set.seed(17)
base <- exp(rnorm(n.users, log(10), 1/2))
response <- c(rbeta(n.users - n.outliers, 9, 1),
              rbeta(n.outliers, 5, 45)) * response.all
actual <- cbind(base %o% rep(1, i.break), 
                base * response %o% rep(response.all, n.periods-i.break))
observed <- matrix(rpois(n.users * n.periods, actual), nrow=n.users)

# ---------------------------- The analysis begins here ----------------------------#

# Alternative presentation of original data 
# 
setwd("eg animation")

for (i in 1:n.users){
    png(paste("line plot", i, ".png"),600,600,res=60)
    plot(c(1,n.periods), c(min(observed), max(observed)), 
        xlab="Time period", ylab="Number of actions", 
        main="Raw data", bty="l", type="n")
    if(i>1){apply(observed[1:i,], 1, function(a) {lines(a, col=rgb(0,100,0,50,maxColorValue=255))})}
    lines(observed[i,], col="black", lwd=2)
    abline(v = i.break, col="Gray")  # Mark the last period before a change
    text(1,60,i)
    dev.off()
}

##
# Then proceed to further analysis eg as set out by @whuber

+1、これはいいアイデアです。windows()またはを使用して新しいデバイスウィンドウを開始し、quartz()その中にfor()ループをネストすることもできます。注:Sys.sleep(1)実際に繰り返しを確認できるように、ループの一番下にを配置する必要があります。もちろん、この戦略では実際にムービーファイルが保存されるわけではありません。もう一度視聴するたびに再実行する必要があります。
GUNG -復活モニカ

+1非常に良いアイデア-次の機会にこれを試します。(例えば、GTW、Mathematicaは、そのようなアニメーションの作成と保存の短い作業を行います。)
whuber

素晴らしいアイデア-これらの線(または生成するコードとデータ)に沿ったアニメーションは、出版物に非常にセクシーなオンライン付録を作成します。
Nブラウワー

7

最も簡単なものの1つは箱ひげ図です。サンプルの中央値がどのように移動し、最も多くの異常値がある日がすぐにわかります。

day <- rep(1:10, 100)
likes <- rpois(1000, 10)
d <- data.frame(day, likes)
library(ggplot2)
qplot(x=day, y=likes, data=d, geom="boxplot", group=day)

ここに画像の説明を入力してください

個々の分析については、データから小さなランダムサンプルを取得し、個別の時系列を分析することをお勧めします。


1
興味深い解決策ですが、私が本当に見たいのは、ユーザーごとの「変更」です。個々のユーザーのアクティビティの変動を見たいです。それが最初にラインを選択した理由ですが、視覚化はあまりにも混乱しています。
規制

まあ、それは本当にあなたがあなたのデータで見たいパターンに依存します、おそらくあなたが何を見つけようとしているのかを私たちに伝えることができれば、私たちは解決策を考え出すことができます。
jem77bfp

私のユーザーのサブセット(トップユーザー)について、特定の日付にロールアウトされたアプリケーションの新しいバージョンが気に入らないユーザーを見つけたいと思います。個々のユーザーによるアクション数の大幅な減少を探しています。
規制する

サイト@ jem77bfpへようこそ。彼はすべてのデータを見たいと言っていました。しかし、詳細があればいいと思います、私は同意します。
ピーターフロム-モニカの復職

+1-ボックスプロットを視覚化する代わりに、折れ線グラフで要約統計量を接続すると便利です。以下の例と説明については、私のこの回答を参照してください。
アンディW

7

確かに。まず、アクションの平均数で並べ替えます。次に、(たとえば)4つのグラフを作成します。各グラフには25の線があり、各四分位に1つです。つまり、y軸を縮小できます(ただし、y軸のラベルはクリアします)。そして、25本の線で、線のタイプと色、そしておそらくプロット記号によってそれらを変えることができ、いくらかの明瞭さを得ることができます

次に、単一の時間軸でグラフを垂直に積み重ねます。

これは、RまたはSASで非常に簡単です(少なくともSAS v。9がある場合)。


2
+1-ただし、小さな倍数ごとにさらに少ない行をお勧めします!このテーマと例に関する関連ブログ投稿を参照してください。ソートも素晴らしいアイデアであり、他の潜在的なものには、ベースラインまたはフォローアップでの値、または変化の測定値(正または負の勾配、変化率など)が含まれます。
アンディW

いいね!コミュニティブログとは何ですか?どのようにアクセスまたは書き込みますか?
ピーターフロム-モニカの復職

3
ブログへの参加方法の詳細については、Skewed Distributionチャットルームに気軽に立ち寄ってください。私たちは常にコミュニティのメンバーからのより多くの貢献に対してオープンです。
アンディW

0

type ifグラフとグラフ設定に関するオプションを使い果たしたとき、アニメーションによる時間の導入が表示する最良の方法であることがわかります。 。主な焦点は、エンドユーザーエクスペリエンスにある必要があります。


ピーター・エリスがここに投稿たソリューションとは異なることを心に留めていましたか?もしそうなら、それについて詳しく説明していただけますか?
whuber

0

個々のユーザーの変更に最も関心がある場合は、スパークラインのコレクションに適した状況です(The Puddingのこの例のように)。

pudding.coolのスパークラインの例

これらは非常に詳細ですが、軸のラベルと単位を削除することで、より多くのチャートを一度に表示できます。

多くのデータツールにはそれらが組み込まれています(Microsoft Excelにはスパークラインがあります)が、Rでそれらをビルドするためにパッケージをプルする必要があると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.