Линейный график имеет слишком много линий, есть ли лучшее решение?

31

Я пытаюсь составить график количества действий пользователей (в данном случае «лайков») с течением времени.

Таким образом, у меня есть «Количество действий» в качестве моей оси Y, моя ось X - время (недели), и каждая строка представляет одного пользователя.

Моя проблема в том, что я хочу посмотреть на эти данные около 100 пользователей. Линейный график быстро превращается в беспорядок с 100 линиями. Есть ли лучший тип графика, который я могу использовать для отображения этой информации? Или я должен смотреть на возможность включения / выключения отдельных линий?

Я хотел бы видеть все данные одновременно, но возможность различать количество действий с высокой точностью не очень важна.

Почему я это делаю

Для подмножества моих пользователей (топ-пользователей) я хочу выяснить, кому из них может не понравиться новая версия приложения, выпущенная в определенный день. Я ищу существенное снижение количества действий отдельных пользователей.

regulatethis
источник
5
Рассматривали ли вы вопрос о том, чтобы сделать линии полупрозрачными, изменив альфа, который используется для их построения?
Fomite
1
@EpiGrad Разумное предложение, но от этого не будет легче увидеть то, что я ищу.
отрегулировать это
1
@regulatethis Я бы предложил подход «небольшого числа», использующий facet_wrapфункцию ggplot2 для создания блока 4х5 диаграмм (4 строки, 5 столбцов - корректировка в зависимости от желаемого соотношения сторон) с ~ 5 пользователями на диаграмму. Это должно быть достаточно ясно, и вы можете масштабировать его примерно до 10 пользователей на график, предоставляя место для 200 с графиком 4x5 или 360 с графиком 6x6.
SlowLearner

Ответы:

31

Я хотел бы предложить (стандартный) предварительный анализ, чтобы устранить основные последствия (а) различий между пользователями, (б) типичного ответа всех пользователей на изменение и (в) типичного отклонения от одного периода времени к следующему ,

Простой (но ни в коем случае не лучший) способ сделать это - выполнить несколько итераций «полировки среднего значения» для данных, чтобы вычистить медианы пользователя и медианы периода времени, а затем сгладить невязки во времени. Определите сглаживания, которые сильно меняются: это пользователи, которых вы хотите подчеркнуть на графике.

Поскольку это данные подсчета, хорошей идеей является их повторное выражение с использованием квадратного корня.

В качестве примера того, что может привести, приведем смоделированный 60-недельный набор данных из 240 пользователей, которые обычно выполняют от 10 до 20 действий в неделю. Изменения во всех пользователях произошли после 40 недели. Трем из них «сказали» негативно отреагировать на изменение. На левом графике показаны необработанные данные: количество действий пользователя (с выделением пользователей по цвету) с течением времени. Как утверждается в вопросе, это беспорядок. Правый график показывает результаты этого EDA - в тех же цветах, что и раньше - с необычно отзывчивыми пользователями, автоматически идентифицированными и выделенными. Идентификация - хотя она и является специальной, - является полной и правильной (в этом примере).

Рисунок 1

Вот Rкод, который произвел эти данные и провел анализ. Это может быть улучшено несколькими способами, в том числе

  • Использование полной средней полировки для поиска остатков, а не только одна итерация.

  • Сглаживание остатков отдельно до и после точки изменения.

  • Возможно, с использованием более сложного алгоритма обнаружения выбросов. Текущий просто помечает всех пользователей, чей диапазон остатков более чем в два раза превышает средний диапазон. Несмотря на простоту, он надежен и работает хорошо. (Настраиваемое пользователем значение 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")
Whuber
источник
3
threshold2.5n.users <- 500n.outliers <- 100threshold <- 2.5
16

Обычно я обнаруживаю, что более двух или трех строк на одном фасете сюжета становится трудночитаемым (хотя я все еще делаю это все время). Так что это интересный пример того, что делать, когда у вас есть что-то, что концептуально может быть 100-гранным сюжетом. Один из возможных способов - нарисовать все 100 граней, но вместо того, чтобы пытаться отобразить их все на странице сразу, просматривая их по одному в анимации.

Мы фактически использовали эту технику в своей работе - мы изначально создали анимацию, показывающую 60 различных линейных графиков в качестве фона для события (запуск новой серии данных), а затем обнаружили, что при этом мы фактически подобрали некоторые особенности данных это не было видно на граненых графиках с 15 или 30 гранями на страницу.

Итак, вот альтернативный способ представления необработанных данных перед тем, как вы начнете удалять пользователя и типичные временные эффекты, как рекомендовано @whuber. Это представляется просто как дополнительная альтернатива его представлению необработанных данных - я полностью рекомендую вам затем продолжить анализ в соответствии с теми, что он предлагает.

Одним из способов решения этой проблемы является создание 100 (или 240 в примере @ whuber) временных рядов по отдельности и их объединение в анимацию. Приведенный ниже код создаст 240 отдельных изображений такого рода, а затем вы можете использовать бесплатное программное обеспечение для создания фильмов, чтобы превратить его в фильм. К сожалению, единственным способом, которым я мог сделать это и сохранить приемлемое качество, был файл размером 9 МБ, но если вам не нужно отправлять его через Интернет, это не может быть проблемой, и в любом случае я уверен, что есть способы обойти это с немного большим количеством анимация подкованная. Анимационный пакет в 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()цикл в него. NB, вам нужно поместить Sys.sleep(1)в конец цикла, чтобы вы могли увидеть итерации. Конечно, эта стратегия на самом деле не сохраняет файл фильма - вам просто нужно перезапускать его каждый раз, когда вы захотите посмотреть его снова.
gung - Восстановить Монику
+1 Очень хорошая идея - я попробую это при следующей возможности. (GTW, Mathematica , например, делает короткую работу по созданию и сохранению таких анимаций.)
whuber
Удивительная идея - анимация по этим направлениям (или код и данные для генерации) сделают очень привлекательное онлайн-приложение к публикации.
N Брауэр
7

Одна из самых простых вещей - это коробочный сюжет. Вы можете сразу увидеть, как движутся ваши выборочные медианы и какие дни имеют наибольшее количество выбросов.

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)

введите описание изображения здесь

Для индивидуального анализа я предлагаю взять небольшую случайную выборку из ваших данных и проанализировать отдельные временные ряды.

jem77bfp
источник
1
Интересное решение, но я действительно хочу увидеть, как происходит «изменение» для каждого пользователя. Я хочу видеть колебания активности для отдельных пользователей. Поэтому изначально я выбрал линию, но визуализация сейчас слишком загромождена.
отрегулировать это
ну, это действительно зависит от того, какие шаблоны вы хотите видеть в своих данных, возможно, если бы вы могли сказать нам, что вы пытаетесь выяснить, мы могли бы найти решение.
jem77bfp
Для подмножества моих пользователей (топ-пользователей) я хочу выяснить, кому из них может не понравиться новая версия приложения, выпущенная в определенный день. Я ищу существенное снижение количества действий отдельных пользователей.
отрегулировать это
Добро пожаловать на сайт @ jem77bfp. он сказал, что хочет увидеть все данные. Но было бы неплохо иметь больше деталей, я согласен.
Питер Флом - Восстановить Монику
+1 - вместо того, чтобы визуализировать коробчатые графики, может быть полезно соединить итоговую статистику в линейных графиках. Смотрите этот мой ответ для примера и обсуждения ниже.
Энди W
7

Конечно. Сначала отсортируем по среднему количеству действий. Затем составьте, скажем, 4 графика, каждый из которых содержит 25 строк, по одному на каждый квартиль. Это означает, что вы можете уменьшить оси Y (но сделать метку оси Y прозрачной). И с 25 линиями, вы можете варьировать их по типу линии и цвету и, возможно, нанесению символа и получить некоторую ясность

Затем сложите графики вертикально с одной временной осью.

Это было бы довольно легко в R или SAS (по крайней мере, если у вас v. 9 из SAS).

Питер Флом - Восстановить Монику
источник
2
+1 - я бы посоветовал еще меньше строк на маленький кратный хотя! Смотрите мой связанный пост в блоге на эту тему и пример. Сортировка также является отличной идеей, и другие потенциальные могут включать в себя значение на исходном уровне или после, или меры изменения (такие как положительный или отрицательный наклон, процентное изменение и т. Д.).
Энди W
Ницца! Что такое блог сообщества? Как получить доступ или написать для него?
Питер Флом - Восстановить Монику
3
не стесняйтесь заходить в чат-рассылку Skewed для получения подробной информации о том, как присоединиться к блогу Мы всегда открыты для большего вклада членов сообщества.
Энди W
0

Я обнаружил, что когда у вас заканчиваются опции, касающиеся типа, если график и графические настройки, введение времени через анимацию является лучшим способом отображения, поскольку оно дает вам дополнительное измерение для работы и позволяет отображать больше информации в удобном для понимания виде. , Ваше основное внимание должно быть сосредоточено на опыте конечного пользователя.

DataDancer
источник
Вы имели в виду что-то, что отличается от решения, которое Питер Эллис разместил здесь ? Если да, не могли бы вы уточнить это?
whuber
0

Если вас больше всего интересуют изменения для отдельных пользователей, возможно, это хорошая ситуация для коллекции Sparklines (как в этом примере из The Pudding ):

Пример спарклайнов от pudding.cool

Они довольно подробные, но вы можете показать гораздо больше графиков одновременно, удалив метки осей и единицы измерения.

Они встроены во многие инструменты для работы с данными (в Microsoft Excel есть спарклайны ), но я предполагаю, что вы захотите собрать пакет для их сборки в R.

bryanbraun
источник