Aus Rohdaten müssen häufig die eigentlichen Variablen generiert werden, die in den Berechnungen, den statistischen Verfahren verwendet werden. Beispiele sind das Bilden von Fragebogenskalen aus im Datensatz vorliegenden Einzelitems, gemittelte Reaktionszeiten aus in den Daten vorliegenden Einzelreaktionen unter einer bestimmten experimentellen Bedingung etc.
Teilweise ist es auch nötig, selbst einen Weg zu finden, um aus vorliegenden Variablen eine inhaltlich sinnvolle Kenngröße zu generieren.
Meist sind Transformationen Operationen, mit denen neue Daten (Variablen) erzeugt werden. Oft ist es gut oder sogar nötig, diese neu erzeugten Variablen als Spalte in einen bestehenden Dataframe zu integrieren.
Die Darstellungen hier konzentrieren sich auf die generellen Techniken zur Datenmodifikation. Bezogen auf die Transformationen in Dataframes gibt es eine eigene Unit. html Es gibt gewisse Überschneidungen bzw. Ergänzungen mit den Darstellungen hier.
Für Datenaufbereitung empfehlen wir den Einsatz von dplyr. Auch dort finden sich entsprechende Hinweise und Beispiele zur Datenaufbereitung.
Es ist essenziell, ein Grundverständnis der Datentypen von R zu haben. Siehe Unit Unit
Zusammenbauen: cbind(), rbind() logic vector als Basis für Filter
In den meisten Statistik-Softwarepaketen gilt die Regel, dass alle Daten einer Beobachtung (Vp) in einer Zeile auftauchen müssen, auch bei Messwiederholungen.
In R gibt es das Long-Format, in dem Messwiederholungen mit einer ‘Gruppierungsvariable’ Beobachtung (Vp) als separate Zeilen in einem Dataframe auftauchen. Die Messwiederholung wird dabei in einer ‘formula’ definiert und entsprechend statistisch behandelt.
Wir schaffen uns eine neue Funktion write.delim()
analog zu read.delim()
, um DataFrames wegzuschreiben
# we get our working directory. This is, where we write to by default.
getwd()
## [1] "/Users/pzezula/ownCloud/lehre_ss_2021/unit/transformation"
# we define a new function called write.delim()
write.delim <- function(x, ...){
write.table(x, quote=F, sep="\t", row.names=F, ...)
}
# we get some data
dd <- read.delim("http://md.psych.bio.uni-goettingen.de/mv/data/div/jump_in.txt")
# we sort dd by gender and height
require(dplyr)
## Loading required package: dplyr
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
dd <- dplyr::arrange(dd, gender, height)
# we save modified dataframe dd to the working directory
write.delim(dd, "gar.txt")
# we save it in csv format with Separator ','
write.csv(dd, "jump_in.csv")
# we save it in csv format with Separator ';'
write.csv2(dd, "jump_in_2.csv")
Women consistently report higher Neuroticism and Agreeableness.
Angenommene Werte: Skalenwerte des BFI-10.
Erzeugen Sie eine Spalte neur_e im Data-Frame df, in der die um den Geschlechtsunterschied bereinigten Neurotizismuswerte stehen.
set.seed(42)
neur.f <- rnorm(100, mean=2.58, sd=.92)
neur.m <- rnorm(100, mean=2.22, sd=.79)
df <- data.frame(
gender = c(rep('f', 100), rep('m',100)),
neur = c(neur.m, neur.f)
)
t.test(neur ~ gender, data=df)
##
## Welch Two Sample t-test
##
## data: neur by gender
## t = -3.8411, df = 183.08, p-value = 0.0001687
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -0.6948043 -0.2232472
## sample estimates:
## mean in group f mean in group m
## 2.150888 2.609914
write.table(df, 'neur_gender.txt', quote=F, sep="\t", row.names=F)
Einer von vielen möglichen Lösungsansätzen
[res_begin:gender]
require(dplyr)
mean.f <- df %>% dplyr::filter(gender == 'f') %>% summarize(mean(neur))
mean.f <- as.numeric(mean.f)
mean.f
## [1] 2.150888
mean.m <- df %>% dplyr::filter(gender == 'm') %>% dplyr::select(neur) %>% summarize(mean(neur))
mean.m <- as.numeric(mean.m)
mean.m
## [1] 2.609914
# grand mean neuroticism
mean.fm <- df %>% summarize(mean(neur))
mean.fm <- as.numeric(mean.fm)
df$neur_e <- NA
df.f <- df %>% dplyr::filter(gender == 'f') %>% dplyr::mutate(neur_e = neur + (mean.fm - mean.f))
df.m <- df %>% dplyr::filter(gender == 'm') %>% dplyr::mutate(neur_e = neur + (mean.fm - mean.m))
df <- rbind(df.f, df.m)
t.test(neur_e ~ gender, data=df)
##
## Welch Two Sample t-test
##
## data: neur_e by gender
## t = 0, df = 183.08, p-value = 1
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -0.2357785 0.2357785
## sample estimates:
## mean in group f mean in group m
## 2.380401 2.380401
[res_end:accept]
… bei missing values in Messwiederholungsverläufen,
Eine Erhebung über 7 Jahre hat aus politischen Gründen in den Jahren 4 und 5 nicht stattgefunden. Die beiden fehlenden Jahre sollen durch geeignete Werte auf individueller Basis geschätzt werden.
require(tidyverse)
## Loading required package: tidyverse
## ── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2 ✓ purrr 0.3.4
## ✓ tibble 3.0.1 ✓ stringr 1.4.0
## ✓ tidyr 1.0.2 ✓ forcats 0.4.0
## ✓ readr 1.3.1
## Warning: package 'ggplot2' was built under R version 3.6.2
## Warning: package 'tibble' was built under R version 3.6.2
## Warning: package 'purrr' was built under R version 3.6.2
## ── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
dd <-tibble(
subj = as.factor(c(1,2,3,4,5)),
y1 = c( 2, 0, 5, 4, 7),
y2 = c( 6, 2, 7, 12, 9),
y3 = c( 7, 9, 9, 15, 15),
y4 = c(NA, NA, NA, NA, NA),
y5 = c(NA, NA, NA, NA, NA),
y6 = c(14, 18, 15, 22, 25),
y7 = c(18, 22, 19, 29, 23)
)
# we convert dd to long format
dd.l <- dd %>% tidyr::gather(year, score, y1:y7) %>% dplyr::arrange(subj)
# and show a graph with the gap between the missing years
dd.l %>% ggplot2::ggplot(aes(x=year, y=score, group=subj, color=subj)) + ggplot2::geom_line() + ggplot2::geom_point(size=5)
## Warning: Removed 10 rows containing missing values (geom_point).
# we interpolate the missings
dd %>% dplyr::mutate(diff_3_6 = y6 - y3, y4 = y3 + diff_3_6/3, y5 = y6 - diff_3_6 / 3) -> dd
# we convert dd to long format
dd.l <- dd %>% tidyr::gather(year, score, y1:y7) %>% dplyr::arrange(subj)
# and show a graph with the gap between the missing years
dd.l %>% ggplot2::ggplot(aes(x=year, y=score, group=subj, color=subj)) + ggplot2::geom_line() + ggplot2::geom_point(size=5)
# btw: we could also think of an individual regression line
Die Akzeptanz einer Lehrveranstaltung soll durch verschiedene Indikatoren erfasst werden.
Es soll ein Akzeptanzscore ‘acceptance’ entwickelt werden, der alle Indikatoren geeignet miteinander verrechnet. Besonders wichtig ist hierbei die Anzahl der Fehltermine. Diese besondere Gewicht soll in dem Score zum Ausdruck kommen. Der Akzeptanzscore soll in Schulnoten (1-6) ausgedrückt werden. Eine 1 ist die höchste Akzeptanz. Begründen Sie Ihren Score und seine Eignung als Akzeptanzscore.
# create sample dataframe
df <-data.frame(
subj =c(1,2,3,4,5),
missing = c( 2, 0, 80, 5, 3),
active = c(90, 10, 2, 20, 80),
constr = c(20, 90, 5, 40, 75),
destr = c(10, 0, 90, 2, 5),
comment = c('spricht viel', 'still aber dabei', 'steigt aus', 'im Mittel', 'aktiv dabei')
)
df
## subj missing active constr destr comment
## 1 1 2 90 20 10 spricht viel
## 2 2 0 10 90 0 still aber dabei
## 3 3 80 2 5 90 steigt aus
## 4 4 5 20 40 2 im Mittel
## 5 5 3 80 75 5 aktiv dabei
Einer von vielen möglichen Lösungsansätzen
[res_begin:accept]
df <-data.frame(
subj =c(1,2,3,4,5),
missing = c( 2, 0, 80, 5, 3),
active = c(90, 10, 2, 20, 80),
constr = c(20, 90, 5, 40, 75),
destr = c(10, 0, 90, 2, 5),
comment = c('spricht viel', 'still aber dabei', 'steigt aus', 'im Mittel', 'aktiv dabei')
)
require(dplyr)
# we invert negative items
# we copy missing to get increased weight
# we select only relevant vars
# we switch to rowwise processing
# we apply rowMeans
# we save result to a new variable
df %>% mutate(i_missing = 100 - missing, i_missing2 = i_missing, i_destr = 100 - destr) %>%
select(i_missing, i_missing2, active, constr, i_destr) %>% rowwise %>% rowMeans -> df$acceptance_percentage
# we convert percentage to grades using full range
(100 - df$acceptance_percentage) / 20 + 1
## [1] 2.04 2.00 5.43 2.52 1.56
# we can round it
round((100 - df$acceptance_percentage) / 20 + 1)
## [1] 2 2 5 3 2
[res_end:accept]
Bei der Vergabe von Studienplätzen gibt es für die Zulassung von Bewerberinnen und Bewerber an einer Ziel-Hochschule das Problem, dass verschiedene Herkunfts-Hochschulen ganz unterschiedliche Benotungskulturen haben. Eine 1.3 der einen Hochschule kann somit eine mittelmäßige Benotung sein, eine 1.7 einer anderen aber eine Spitzennote. Entwickeln Sie einen Ansatz, die Noten vergleichbarer zu machen. Generieren Sie für jede Person des Datensatzes eine Note, die die Leistungen der Personen besser vergleichbar macht.
grade1, grade2 und grade3 seien Noten in Psychologie in verschiedenen Fächern, z. B. Methoden, Diagnostik, angewandte Psychologie.
# create sample dataframe
df <-data.frame(
subj = c(1, 2, 3, 4, 5, 6, 7, 8, 9),
uni = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
grade1 = c(1.0, 2.7, 3.7, 1.3, 1.7, 1.0, 3.3, 4.0, 3.7),
grade2 = c(4.0, 3.0, 1.3, 1.3, 1.0, 1.3, 2.7, 4.0, 3.3),
grade3 = c(1.3, 2.3, 2.7, 1.0, 1.3, 1.3, 2.3, 3.7, 3.0)
)
df
## subj uni grade1 grade2 grade3
## 1 1 1 1.0 4.0 1.3
## 2 2 1 2.7 3.0 2.3
## 3 3 1 3.7 1.3 2.7
## 4 4 2 1.3 1.3 1.0
## 5 5 2 1.7 1.0 1.3
## 6 6 2 1.0 1.3 1.3
## 7 7 3 3.3 2.7 2.3
## 8 8 3 4.0 4.0 3.7
## 9 9 3 3.7 3.3 3.0
[res_begin:grades]
# create sample dataframe
df <-data.frame(
subj = c(1, 2, 3, 4, 5, 6, 7, 8, 9),
uni = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
grade1 = c(1.0, 2.7, 3.7, 1.3, 1.7, 1.0, 3.3, 4.0, 3.7),
grade2 = c(4.0, 3.0, 1.3, 1.3, 1.0, 1.3, 2.7, 4.0, 3.3),
grade3 = c(1.3, 2.3, 2.7, 1.0, 1.3, 1.3, 2.3, 3.7, 3.0)
)
#todo
# individual mean of grades
require(dplyr)
# inner select works
mutate(df, grade.mean = rowMeans(select(df, contains("grade")), na.rm = TRUE)) -> df.n
# chained select works
df %>% select(contains("grade")) %>% mutate(gr.mean = rowMeans(., na.rm = TRUE)) -> df.n
# we could alternatively discard the source variables
df %>% select(contains("grade")) %>% transmute(gr.mean = rowMeans(., na.rm = TRUE)) -> df.n
# we add result to df in a new column named 'gr.mean'
df$gr.mean <- df.n[,1]
df$gr.mean <- df.n[['gr.mean']]
# : works
df %>% select(grade1:grade3) %>% mutate(gr.mean = rowMeans(., na.rm = TRUE)) -> df.n
# using mean() which needs an input vector
df %>%dplyr::select(contains("grade")) %>% dplyr::rowwise() %>% dplyr::mutate(av_grade = mean(c(grade1, grade2, grade3))) -> df.n
df %>% dplyr::rowwise() %>% dplyr::mutate(av_grade2 = mean(c(grade1, grade2, grade3))) -> df.n
df %>% dplyr::rowwise() %>% dplyr::mutate(av_grade3 = mean(c(select(., grade1:grade3)))) -> df.n
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
## Warning in mean.default(c(select(., grade1:grade3))): argument is not numeric or
## logical: returning NA
rm(df.n)
# rowMeans only works on data frames
df %>%dplyr::select(contains("grade")) %>% dplyr::rowwise() %>% dplyr::mutate(av_grade = rowMeans(data.frame(grade1, grade2, grade3))) -> df.n
# Versuche
df <- expand.grid(x = 1:3, y = 3:1, z = 4:6)
df %>% rowwise() %>% do(i = seq(.$x, .$y)) %>% summarise(n = length(i))
## # A tibble: 27 x 1
## n
## <int>
## 1 3
## 2 2
## 3 1
## 4 2
## 5 1
## 6 2
## 7 1
## 8 2
## 9 3
## 10 3
## # … with 17 more rows
df %>%dplyr::select(contains("grade")) -> df.g
require(dplyr)
ds <- tbl_df(mtcars)
[res_end:grades]
In einem Experiment haben die Versuchspersonen unterschiedlich häufig reagiert.
Angenommen, normalerweise wurden 25 Reaktionen erwartet. - Entwickeln Sie eine prozentuale Reaktionshäufigkeit (Under- Overachiever)
Robuste Schätzer: Von einer Menge von Reaktionszeiten soll pro Versuchsperson eine fixe Anzahl von extremen Werten nicht in die Auswertungen eingehen.
Dieselbe Grundidee soll über einen Prozentsatz gelöst werden. Beispiel: die 2% schnellsten und die 5% langsamsten Reaktionszeiten sollen nicht in eine Analyse eingehen.
df <- read.delim("http://md.psych.bio.uni-goettingen.de/mv/data/virt/v_transf_rt.txt")
df
## subj V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18
## 1 1 413 484 363 420 492 497 437 476 393 388 449 414 405 373 428 437 401 407
## 2 2 447 406 438 434 486 513 398 570 375 560 457 435 572 437 482 448 431 NA
## 3 3 435 402 454 456 501 431 383 423 366 376 380 346 423 456 415 372 456 344
## 4 4 458 450 458 415 409 404 383 344 420 401 472 360 345 451 403 500 454 494
## 5 5 341 363 404 410 458 392 400 393 413 453 399 416 366 409 376 478 374 370
## 6 6 446 424 387 479 445 453 349 511 462 368 428 437 408 428 359 455 415 431
## 7 7 485 487 443 442 433 540 362 529 353 441 362 461 517 452 457 331 NA NA
## 8 8 569 550 367 554 539 475 515 484 565 530 548 474 546 560 558 NA NA NA
## 9 9 432 418 497 440 396 476 526 405 377 496 475 444 349 484 477 487 NA NA
## 10 10 406 372 399 452 478 399 386 461 384 472 424 415 447 411 416 405 490 458
## V19 V20 V21 V22 V23 V24 V25 V26 V27 V28 V29 V30
## 1 407 370 427 457 364 397 360 NA NA NA NA NA
## 2 NA NA NA NA NA NA NA NA NA NA NA NA
## 3 NA NA NA NA NA NA NA NA NA NA NA NA
## 4 468 NA NA NA NA NA NA NA NA NA NA NA
## 5 405 411 360 403 NA NA NA NA NA NA NA NA
## 6 437 490 446 464 441 431 397 425 383 445 511 NA
## 7 NA NA NA NA NA NA NA NA NA NA NA NA
## 8 NA NA NA NA NA NA NA NA NA NA NA NA
## 9 NA NA NA NA NA NA NA NA NA NA NA NA
## 10 405 390 NA NA NA NA NA NA NA NA NA NA
Lösungsansatz
[res_begin:rt]
df <- read.delim("http://md.psych.bio.uni-goettingen.de/mv/data/virt/v_transf_rt.txt")
# we define a function to get the number of NA
n.of.na <- function(x) length(which(is.na(x)))
# we apply this funciton rowwise
df$n.na <- apply(df[2:31], 1, n.of.na)
require(dplyr)
#df %>% select(V1:V30) %>% rowwise %>% mutate(nna = n.of.na(c(V1,V2,V30)))
#todo
# {}todo
## von Joshua
ddf <- read.delim("http://md.psych.bio.uni-goettingen.de/mv/data/virt/v_transf_rt.txt")
# we define a function to get the number of NA
n.of.na <- function(x) length(which(is.na(x)))
# we apply this function rowwise
ddf$n.na <- apply(ddf[2:31], 1, n.of.na)
#dieser Befehl fügt einfach die ANzahl fehlender Reaktionszeite ein
require(dplyr)
#für a)
dplyr::percent_rank(ddf$n.na)->ddf$ProzRang #ginge glaube ich auch in mutate, geht so aber fixer.
#Dass zwei werte den gleichen Rang kriegen können, ist durchaus ok, würde dich ja interessieren, wenn zwei leute die wenigsten zeiten haben
#Prozentränge, weil deren Minimum immer 0, und Maximum immer 100 ist, dann muss man den maximalrang nicht noch aus der stichprobengröße rausholen
dfMeisteZeiten<-dplyr::filter(ddf, ddf$ProzRang==0)
dfWenigsteZeiten<-dplyr::filter(ddf, ddf$ProzRang==1)
#Wer ist das denn nun?
#die meisten Zeiten hat Subjekt NUmmer:
dfMeisteZeiten$subj
## [1] 6
#und die wenigsten hat SUbjekt NUmmer:
dfWenigsteZeiten$subj
## [1] 8
#für b)
#mutate(rowwise(ddf),MinimumRT = min(c(V1:V30),na.rm=T))->ddf
#das funktioniert nicht. hab jetzt 1,5 Stunden dran gekämpft, irgendwie nehmen rowwise-Befehle keine na.rm-Argumente an.
#Wenn du das so in der Klausur schreibst, und das dazu sagst, sollte das für alle Punkte reichen, das ist echt tiefschwarze Programmiermagie
#für c)
mutate(rowwise(ddf), ProzErwart = (30-n.na)/25)->ddf
#Und dann wie bei dir, mehr als 1 Over-, weniger Underachiever
[res_end:grades]
Beobachtungseinheit what is my observation, countries can be observations if we consider the world or if we compare world regions cities might be the observations within a country, persons aren’t in this case, although persons live in cities
verschieben, strecken, stauchen, z-transformieren
zwei Richtungen, zwei Möglichkeiten
dd <- readRDS(gzcon(url("http://md.psych.bio.uni-goettingen.de/mv/data/div/parms_data.rds")))
psych::describe(dd$ev)
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 300 5.23 2.48 5.26 5.27 2.78 -1.7 10.33 12.03 -0.2 -0.77 0.14
# move ev so that it varies between -5 and +5, shift is usually an addition or subtraction,
# no stretching, no compressing, distances stay the same
# transformation base is the scale range
dd$ev.c <- dd$ev -5
# centering rv, no rescaling, movement (distance) depends on the empirical mean
dd$rv.c <- dd$rv - mean(dd$rv)
# stretch/shrink relations is done with * and /
# recalculate dd$ev to a range between 0 and 100
dd$ev.100 <- dd$ev * 10
# rescaling explicitly
dd$rv.zm <- (dd$rv - mean(dd$rv)) / sd(dd$rv)
# ... or using scale()
dd$rv.z <- scale(dd$rv)
# rescale rv to be a normal distribution with mean 50 and sd 10, T-values
dd$rv.t <- scale(dd$rv) * 10 + 50
Rmisc::summarySE(dd, "ev.c")
## .id N ev.c sd se ci
## 1 <NA> 300 0.2293757 2.477558 0.1430419 0.2814963
psych::describe(dd[,6:11])
## vars n mean sd median trimmed mad min max range skew
## evl 1 300 4.01 2.70 3.89 3.99 2.84 -5.70 13.00 18.70 0.17
## evh 2 300 5.24 2.98 5.19 5.23 3.97 -1.93 11.38 13.30 0.03
## ev.c 3 300 0.23 2.48 0.26 0.27 2.78 -6.70 5.33 12.03 -0.20
## rv.c 4 300 0.00 15.07 2.48 1.03 13.23 -48.21 40.71 88.92 -0.66
## ev.100 5 300 52.29 24.78 52.63 52.74 27.77 -17.04 103.28 120.33 -0.20
## rv.zm 6 300 0.00 1.00 0.16 0.07 0.88 -3.20 2.70 5.90 -0.66
## kurtosis se
## evl 0.44 0.16
## evh -1.09 0.17
## ev.c -0.77 0.14
## rv.c 0.34 0.87
## ev.100 -0.77 1.43
## rv.zm 0.34 0.06
Verhältnisse sind Brüche
Dreisatz, was sind die 100 Prozent
Verteilungsform verändern quadrieren, wurzeln, logarithmieren, …
Kreativität, etwas neu denken individuelles lm()
# Leistungen an Tagen, zwei fehlen
rv <- c(10, 11, NA, NA, 14, 17, 18, 20)
dd <- tibble(
rv = rv,
tt = 1:8
)
mm <- lm(rv ~ tt, data=dd)
dd.n <- tibble(tt = 1:length(rv))
predict.lm(mm, dd.n)
## 1 2 3 4 5 6 7 8
## 9.570815 10.987124 12.403433 13.819742 15.236052 16.652361 18.068670 19.484979
Tutorial von Christian Treffenstädt
Beispiel-Datenfile
stud bzw. in ISO-8859-1 (ISO Latin 1) stud latin1
# read stud data
tryCatch(
stud <- read.delim("stud_utf8.txt"),
finally = {cat('r'); stud <- read.delim("http://md.psych.bio.uni-goettingen.de/mv/data/div/stud_utf8.txt", fileEncoding="UTF-8")}
#finally = stud <- read.delim("stud_utf8.txt")
)
## r
head(stud)
## no height shoe_size weight gender birth_month birth_year statistics_grade
## 1 1 177 43.0 75 1 3 88 3.0
## 2 2 190 45.0 87 2 6 83 4.0
## 3 3 162 37.0 49 1 1 89 3.2
## 4 4 179 42.5 80 2 6 85 3.0
## 5 5 178 39.0 52 1 7 85 2.7
## 6 6 186 44.0 76 2 2 89 2.7
## abitur math_intense academic_background computer_knowledge string
## 1 1.6 0 1 7 grün
## 2 3.7 0 1 5 groß
## 3 2.1 0 0 4 öde
## 4 2.0 1 1 5 süß
## 5 2.2 0 1 2 ärgerlich
## 6 1.7 1 0 5 Ärger
Version: 06 April, 2021 19:40