Laden der Libraries und der Daten

library(tidyverse)
## ── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.2.1     ✓ purrr   0.3.3
## ✓ tibble  2.1.3     ✓ dplyr   0.8.3
## ✓ tidyr   1.0.0     ✓ stringr 1.4.0
## ✓ readr   1.3.1     ✓ forcats 0.4.0
## ── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(stringr)
library(modelr)

Laden der Daten: Achtung, das sind 15MB an Daten! Sie stammen von meiner eigenen Seite für den Oktober 2019. Sie wurden aus einer stark angepassten Implementierung von Google Analytics generiert.

data <- read_csv("https://alby.link/websiteboostingdata", 
    col_types = cols(dimension3 = col_character()))

Wir schauen uns die Daten einmal an:

data

Die Spalten enthalten die folgenden Daten:

Data Cleaning

Die Daten sind noch nicht ganz sauber. Mit der Library Stringr haben wir die Möglichkeit reguläre Ausdrucke zu nutzen, um Muster zu entfernen.

data <- data %>%
  mutate(pagePath = str_replace(pagePath,"\\?highlight.*","")) %>%
  mutate(pagePath = str_replace(pagePath,"\\?fbclid.*","")) %>%
  mutate(pagePath = str_replace(pagePath,"\\?s=.*","search")) %>%
  mutate(pagePath = str_replace(pagePath,"\\?.*","")) 

Besonders die Facebook-ClickID ist nervig, da eine URL dann mit verschiedenen IDs zu unterschiedlichen URLs wird. Die Reihenfolge der Mutationen ist wichtig!

Transformation von Daten

Wir erstellen zunächst einmal eine Liste der Seiten mit der Häufigkeit ihrer jeweiligen Aufrufe:

(top_pages <- data %>%
  filter(dimension2 == "pageview") %>%
  group_by(pagePath) %>%
  summarize(n=n()) %>%
  arrange(desc(n)))

Danach erstellen wir eine Liste aller Seiten und wie häufig sie bis “zum Ende” gelesen worden sind, in dem Fall meiner Seite bis das YARPP-Element auf dem Bildschirm des Nutzers sichtbar ist.

(cv <- data %>%
  group_by(pagePath) %>%
  filter(eventLabel == "YARPP Visibility") %>%
  summarize(cv = n()) %>%
  arrange(desc(cv)))

Die einfachste Lösung wäre nun, eine Art Conversion Rate auszurechnen, indem die Conversions durch die Anzahl der Seitenaufrufe geteilt wird. Wir sortieren die Daten nach der geringsten Conversion Rate, denn wir wollen ja wissen, welche Seiten optimiert werden müssen.

new_ga_data <- merge(top_pages,cv)
new_ga_data %>%
  mutate(cvr = cv/n) %>%
  filter(n >50) %>%
  arrange(cvr)

Analyse mit Residuen

Wir schauen uns aber noch eine andere Lösung an. Wir plotten die Impressions und die Anzahl der Conversions pro Seite und ziehen dann mit einer linearen Regression eine Linie durch die Punkte. Die Linie zeigt nun die erwartete Anzahl von Conversions für eine Anzahl von Impressions. Die Punkte über und unter der Linie sind also über oder unter den Erwartungswerten. Der Abstand zwischen einem solchen Punkt und der Linie wird Residuum genannt.

plot(new_ga_data$n,new_ga_data$cv, xlab = "Impressions", ylab = "Read until the end")
mod <- lm(new_ga_data$cv ~ new_ga_data$n, data = new_ga_data)
abline(mod)

Und jetzt plotten wir einfach mal die Residuen, also nur die Abstände von den Erwartungswerten:

tada <- new_ga_data %>%  # "tada" war jetzt nicht so die vorbildliche Benennung für einen Data frame, sorry!
  add_residuals(mod)

tada %>% 
  filter(n > 50) %>%
  mutate(myPagePath = str_trunc(pagePath, 30)) %>%
  ggplot(aes(myPagePath, resid, group = 1)) + 
  theme(axis.text.x = element_text(angle = 90)) +
  geom_ref_line(h = 0) + 
  geom_line()

Warum sehen die Daten hier so anders aus als die CVR-Daten? Die Seite über die Synology-Uploadgeschwindigkeit hat eine CVR von über 83%, aber das Residuum ist nur knapp über der Linie. Da die Synology-Seite relativ selten aufgerufen worden ist, ist der Abstand der tatsächlichen zur erwarteten Conversion Rate nicht so hoch wie der über Mensch gegen Maschine. Warum macht man das überhaupt? Zum Beispiel weil bei einer geringen Anzahl von Seitenaufrufen die Aussagekraft der Conversion Rate eventuell nicht so hoch ist.

Wie sieht die Liste nun aus?

tada %>%
  select(resid,pagePath) %>%
  filter(resid < 0) %>%
  arrange(resid)

Zugabe (nicht in der Website Boosting): Wir können einen Kompromiss finden zwischen der Conversion Rate und den Residuen, indem wir die Daten logarithmisieren

mod <- lm(log(new_ga_data$cv) ~ log(new_ga_data$n), data = new_ga_data)

tada <- new_ga_data %>% 
  add_residuals(mod)

tada %>% 
  filter(n > 50) %>%
  mutate(myPagePath = str_trunc(pagePath, 30)) %>%
  ggplot(aes(myPagePath, resid, group = 1)) + 
  theme(axis.text.x = element_text(angle = 90)) +
  geom_ref_line(h = 0) + 
  geom_line()

Wie sieht die Reihenfolge hier aus?

tada %>%
  select(resid,pagePath) %>%
  filter(resid < 0) %>%
  arrange(resid)

Fazit

Wir haben uns drei verschiedene Methoden angesehen, um Seiten zu identifizieren, die häufiger als andere nicht zuende gelesen werden:

Die Ergebnisse unterscheiden sich, indem sie die Anzahl der Impressions und der Conversions unterschiedlich stark gewichten.