Scoring questionnaires using the ‘qscorer’ package

Motivation

Health-related questionnaires are used in many psychological studies and clinical trials. I know from my own experience that information about scoring procedures are often scattered among numerous sources and, thus, hard to find. Since I frequently work with questionnaires, I have decided to write qscorer, an R package with scoring procedures for health-related questionnaires.

plot of chunk unnamed-chunk-1

Scoring procedures

The qscorer package (version 0.3.0) provides procedures for scoring the following health-related questionnaires:

  • Atrial Fibrillation Effect on QualiTy-of-Life Questionnaire (AFEQT)
  • Beck Depression Inventory (BDI, BDI-II)
  • Behavior Rating Inventory of Executuve Function, adult version (BRIEF-A)
  • Dutch Eating Behavior Questionnaire, German version (DEBQ)
  • Eating Disorder Examination Questionnaire, short form (EDE-Q8)
  • Epworth Sleepiness Scale (ESS)
  • European Quality of Life Five Dimension Three Level Scale Questionnaire (EQ-5D-3L)
  • General Self-Efficacy Scale (GSES)
  • Hospital Anxiety and Depression Scale (HADS)
  • Impact of Weight on Quality of Life-Lite Questionnaire (IWQOL-Lite)
  • International Physical Activity Questionnaire, short form (IPAQ)
  • Patient Health Questionnaire-9 (PHQ-9)
  • Patient Health Questionnaire-15 (PHQ-15)
  • Rosenberg Self-Esteem Scale (RSES)
  • Severe Respiratory Questionnaire (SRI)
  • Skala zur Selbstregulation (REG) (German)
  • Social Support Questionnaire, short form (F-SozU-K-7) (German)
  • Weight Bias Internalization Scale (WBIS)
  • Weight Self-Stigma Questionnaire (WSSQ)
  • Yale Food Addiction Scale, Version 2.0 (YFAS V2.0)

Documentation

The package documentation is hosted by GitHub Pages.

Installation

You can install the development version of qscorer from GitHub with:

devtools::install_github('nrkoehler/qscorer')

To Do

In the near future, I will add control structures to the scoring functions in order to prevent items with out-of-range values to be scored. qscorer is a growing package. Thus, further scoring procedures will be added.

Advertisements
Posted in Fav R Packages | Tagged , | Leave a comment

Statistische Grundlagen mit R: Streuungsmaße

Nachdem ich mich im ersten Teil meiner Serie “Statistische Grundlagen mit R” mit Maßen der zentralen Tendenz beschäftigt habe, erkläre ich heute die Bedeutung verschiedener Steuungsmaße.

Streuungsmaße

Um einen Datenvektor möglichst treffend zu beschreiben, reicht die Kenntnis der Zentralitätsmaße in der Regel nicht aus. Stellen wir uns z.B. vor, dass an einer Grundschule ein Altpapiersammelwettbewerb durchgeführt wird, bei dem die drei Schulklassen, die am meisten Papier gesammelt haben Preise bekommen: die erstplatzierte Klasse 100€, die zweitplatzierte Klasse 50€ und die drittplatzierte Klasse 20€. Wenn wir uns vorstellen, dass die erstplatzierte Klasse insgesamt 800 kg, die zweitplatzierte Klasse 700 kg und die drittplatzierte Klasse 600 kg gesammelt habt, würde uns die Vergabe der Preise im Großen und Ganzen gerecht erscheinen. Stellen wir uns hingegen vor, dass die erstplatzierte Klasse 701 kg, die zweitplatzierte Klasse genau 700 kg und die drittplatzierte Klasse 699 kg Papier gesammelt hat, erscheint die Preisvergabe weniger angemessen zu sein, weil die Unterschiede zwischen den gesammelte Altpapiermengen äußerst klein sind und möglicherweise sogar auf Messungenauigkeiten (beim Wiegen des Papiers) zurückzuführen sind. Berechnen wir den Mittelwert für beide Szenarien, sehen wir jedoch keinen Unterschied; beide Mittelwerte sind identisch:

mean(c(600, 700, 800)) == mean(c(699, 700, 701))
## [1] TRUE

Mit Streuungsmaßen lässt sich, wie der Name schon sagt, die Streuung bzw. Entfernung der einzelnen Datenpunkte von einem Zentralitätsmaß beschreiben.

Spanne

Das wohl einfachste Streuungsmaß ist die Spanne (engl.: Range). Diese lässt sich berechnen, indem man den kleinsten Wert vom größen des Datenvektors abzieht:

spanne1 <- 800 - 600
spanne2 <- 701 - 699

Obwohl beide Datenvektoren denselben Mittelwert haben, unterscheiden sich die Spannen deutlich (200 vs. 2). Die Spanne ist als Streuungsmaß allerdngs nur sehr eingeschränkt zu gebrauchen, weil sie sehr stark durch Extremwerte beeinflusst wird. So könnte man sich vorstellen, dass bei dem besagten Altpapiersammelwettbewerb 8 von 10 Schulklassen zwischen 400 und 500 kg Altpapier sammeln, eine Klasse 1.000 kg und eine Klasse überhaupt kein Papier. Obwohl die meisten Datenpunkte zwischen 400 und 500 kg liegen, nimmt die Spanne den Wert 1.000 (1.000 – 0) an.

Abweichungen vom Mittelwert

Eine weitere Möglichkeit zur Beschreibung der Streuung eines Datenvektors ist die Abweichung der einzelnen Datenpunkte vom Mittelwert aller Datenpunkte. Bezugnehmend auf unser Beispiel, weichen im ersten Szenario zwei von drei Datenpunkten um jeweils 100 kg vom Mittelwert ab, während der dritte Wert mit dem Mittelpunkt identisch ist. Bildet man die Summe dieser Abweichungen, erhalten wir das folgende Ergebnis:

-100 + 0 + 100
## [1] 0

Berechnet man die Streuung nach dieser Gleichung, muss diese zwangsläufig den Wert 0 ergeben, da sich positive und negative Differenzen gegenseitig aufheben. Um dies zu verhindern, ließen sich z.B. die absoluten Beträge der Differenzen aufaddieren. In der Statistik entledigt man sich des Minuszeichens jedoch durch eine Quadrierung der einzelnen Differenzen.

Summe der quatrierten Abweichungen

Die einzelnen quatrierten Differenzen werden zu einer Quadratsumme zusammengefasst. Für unsere beiden Szenarien der Altpapiersammelaktion lassen sich die Quadratsummen folgendermaßen berechnen:

(qs1 <- (-100)^2 + 0^2 + 100^2)
## [1] 20000
(qs2 <- (-1)^2 + 0^2 + 1^2)
## [1] 2

Als Streuungsmaß hat die Quadratsumme jedoch den Nachteil, dass ihr Betrag mit steigender Anzahl an Datenpunkten zunimmt.

Varianz

Um eine Abhängikeit des Streungsmaßes von der Anzahl der Datenpunkte zu verhindern, lässt sich die Summe der quatrierten Abweichungen durch die Anzahl der Datenpunkte (Beobachtungen) teilen. Mit dieser Gleichung haben wir die Varianz eines Datenvektors definiert. Um zu demonstrieren, wie sich die Varianz eines Datenvektors mit R berechnen lässt, kommen wir wieder auf unser Beispiel vom Altpapiersammelwettbewerb zurück:

altpapier <- c(600, 700, 800) # vgl. Szenario 1
(altpapier - mean(altpapier)) ^ 2 # Liste quatrierter Abweichungen vom Mittelwert
## [1] 10000     0 10000
sum((altpapier - mean(altpapier)) ^ 2) # Summe der quatrierten Abweichungen
## [1] 20000
(varianz <- sum((altpapier - mean(altpapier)) ^ 2) / length(altpapier)) # Teile durch Anzahl der Beobachtungen
## [1] 6666.667

Standardabweichung

Die Interpretation der Varianz gestaltet sich jedoch schwierig, da sie die Einheiten des Datenvektors (in unserem Beispiel Kilogramm) quatriert widergibt. So hat der Vektor altpapier eine Varianz von etwa 6666.7 kg². Um die Standardabweichung zu berechnen, braucht man nun nur noch die Quatratwurzel der Varianz ziehen:

sqrt(varianz)
## [1] 81.64966

Im Gegensatz zur Varianz, hat die Standardabweichung dieselbe Einheit wie der Datenvektor, in unserem Fall etwa 81.6 kg (Szenario 1) und 0.816 kg (Szenario 2).

Empfohlene Literatur

  • Jeffrey M. Stanton, Reasoning with Data: An Introduction to Traditional and Bayesian Statistics Using R, Guilford Press: 2017.
Posted in Indroduction | Tagged | Leave a comment

Statistische Grundlagen mit R: Maße der zentralen Tendenz

Beginnend mit dem heutigen Tag veröffentliche ich auf “Scripts and Statistics” einige Beiträge unter der Überschrift “Statistische Grundlagen mit R”. Im ersten Teil erkläre ich die drei wichtigsten Maße der zentralen Tendenz.

Maße der zentralen Tendenz

Mit Maßen der zentralen Tendenz lässt sich ein Datenvektor (eine Reihe von Zahlen) mit einem Kennwert charakterisieren. Die drei häufigsten Zentralitätsmaße sind Mittelwert, Median und Modus.

Mittelwert

Das wohl bekannteste statistische Maß ist das arithmetische Mittel, auch Mittelwert genannt (engl.: “mean”). Der Mittelwert wird berechnet, indem man alle Werte eines Datenvektors addiert und die Summe durch die Anzahl der Einzelbeobachtungen (Länge des Vektors) teilt. So lässt sich z.B. die Anzahl der an einem Spieltag der Fußball-Bundesliga erzielten Treffer pro Spiel wie folgt darstellen:

treffer <- c(2, 3, 6, 0, 1, 3, 9, 3, 2)

Mit der c()-Funktion wird ein Datenvektor erzeugt und unter der Bezeichnung treffer abgespeichert. Um den Mittelwert zu berechnen, müssen alle Zahlen dieses Vektors addiert und durch 9 (Anzahl der Spiele pro Spieltag) dividiert werden:

sum(treffer) / length(treffer) # Ausführliche Berechnung
## [1] 3.222222
mean(treffer) # Mit R-Funktion berechnet
## [1] 3.222222

In der ersten Zeile wird der Mittelwert – wie soeben erklärt – berechnet, in der zweiten Zeile mit der mean()-Funktion. Beide Rechenwege kommen auf dasselbe Ergebnis.

Median

Der Median (engl.: “median”) lässt sich etwas salopp auch als “Halbe-Strecke-Wert” (halfway value) bezeichnen. Ordnet man einen Datenvektor aufsteigend vom kleinsten bis zum größten Wert und sucht sich den Wert, der sich genau in der Mitte befindet, so erhält man den Median. Mit der sort()-Funktion wird der Datenvektor aufsteigend geordnet:

sort(treffer)
## [1] 0 1 2 2 3 3 3 6 9

Da der Datenvektor treffer aus neun Einzelwerten besteht, muss der fünfte und damit mittlere Wert dem Median entsprechen:

sort(treffer)[5] # Median als mittlerer Wert des sortierten Datenvektors
## [1] 3
median(treffer) # Mit R-Funktion berechnet
## [1] 3

Beide Werte sind identisch. Wie wir sehen, ist der Median mit 3 Treffern ewtas kleiner als der Mittelwert mit 3.2222222 Treffern. Dieses Ergebnis weist auf einen wichtigen Vorteil des Medians hin: dieser ist robuster als der Mittelwert in Hinblick auf Extremwerte (engl.: “outliers”). Ein Extremwert ist ein Wert, der deutlich höher oder niedriger ist als die meisten anderen Werte. In unserem Beispiel sind die 9 Tore, die bei einem Bundesligaspiel erzielt wurden, ein Extremwert.

Modus

Der Modus (engl.: “mode”) ist der häufigste Wert des Datenvektors. Tritt ein bestimmter Wert eines Datenvektors besonders häufig auf, so lässt sich mit dem Modus der typischste Wert dieses Vektors angeben. Der Modus ist in noch stärkerem Maße als der Median robust gegenüber Extremwerten. Da in den R-Basispaketen keine Funktion zur Berechnung des Modus implementiert ist, muss ein zusätzliches R-Paket (DescTools) installiert und geladen werden. Mit der Mode()-Funktion dieses Pakets lässt sich nun der Modus unseres Datenvektors berechnen:

install.packages('DescTools')
library(DescTools)
Mode(treffer)
## [1] 3

Der Modus zeigt an, dass pro Bundesligaspiel am häufigsten 3 Tore fallen.

Resumee

Anhand der soeben erläuterten Maße der zentralen Tendenz ist eine zusammenfassende Beschreibung eines Datenvektors möglich. Der Mittelwert liefert uns einen präzisen, arithmetischen Mittelwert des Datenvektors, ist aber vergleichsweise anfällig gegenüber Extremwerten. Den Median kann man sich gut als mittleren Punkt einer Strecke vorstellen, der diese Strecke in zwei gleich große Hälften teilt. Wenn sich Mittelwert und Median unterscheiden, ist dies ein Hinweis darauf, dass der Datenvektor Extremwerte beinhaltet, die sich von den gewöhnlichen Werten stark unterscheiden. Der Modus zeigt uns, welcher Wert des Datenvektors am häufigsten vorkommt.

Empfohlene Literatur

  • Jeffrey M. Stanton, Reasoning with Data: An Introduction to Traditional and Bayesian Statistics Using R, Guilford Press: 2017.
Posted in Indroduction | Tagged | Leave a comment

How to Vectorize a Function in R

Last year I came across the base R function Vectorize(). Vectorize() vectorizes the action of a non-vectorized function. Let's give an example.

In one of my current research projects, I need to hash patient ids to fulfill the requirements of data privacy protection. With sha1(), the digest package contains a function to calculate a hash of an object. Let's see what the function does, when we apply it to a column of the mtcars data frame:

First, we write the row names (names of the cars) into a new variable ('NAME'):

library(dplyr)
library(tibble)

data("mtcars")
mtcars <- mtcars %>%
  tibble::rownames_to_column('NAME')

Now, we assume that 'NAME' is the id variable we want to hash:

library(digest)

mtcars <- mtcars %>%
  mutate(HASH = sha1(NAME)) %>%
  select(NAME, HASH, mpg)

head(mtcars)
##                NAME                                     HASH  mpg
## 1         Mazda RX4 cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 21.0
## 2     Mazda RX4 Wag cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 21.0
## 3        Datsun 710 cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 22.8
## 4    Hornet 4 Drive cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 21.4
## 5 Hornet Sportabout cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 18.7
## 6           Valiant cbe2ae3f7e5a2c558d4c36cad5e27a906e8aef8d 18.1

As we can see, different car names received the same hash. This is not exactly what we want. It happened because the sha1() function is not vectorized.

In the final step, we vectorize the sha1() function and apply it once again to the mtcars data frame:

sha1_vectorized <- Vectorize(digest::sha1)
mtcars <- mtcars %>%
  mutate(HASH = sha1_vectorized(NAME)) %>%
  select(NAME, HASH, mpg)

head(mtcars)
##                NAME                                     HASH  mpg
## 1         Mazda RX4 b22967895db5fb044febfaad31d34ccfc95f4440 21.0
## 2     Mazda RX4 Wag 45464747af0f4df66ee253bfef89d4b106cfb713 21.0
## 3        Datsun 710 785ba328b314246358feec3166fafa71bb724793 22.8
## 4    Hornet 4 Drive e1265538639ccf3f772038fe3db16aaaa28a4dd9 21.4
## 5 Hornet Sportabout 0b3f30b312e17c7c610399bf204ea9de2c71b96e 18.7
## 6           Valiant fe5206e3d182bff5748e295f9f78dba99ed0ec7f 18.1

Bingo! The vectorized version of sha1() did the job!

PS: Vectorizing a function makes the function perform the same operation on every entry in a data structure (but with different values) (see Win-Vector Blog). The non-vectorized sha1() function seems to treat the variable NAME as a scalar (a single value). Thus, it hashes not every single entry of the variable, but all elements of the variable on the whole.

Posted in Tips & Tricks | Tagged , , | Leave a comment

R Markdown Inline Code: Adding a Conjunction to Listings

In my last blog post, I wrote a couple of lines about EFFECT, a clinical trial I'm currently involved in. EFFECT is a cross-over trial with two wash-out and two study phases. After each of the four phases, the participating hospitals receive a summary of some study results. When I write these summary reports using R Markdown, I put a character string with the names of the ICUs into an inline R expression.

The character vector containing the names of the ICUs, I usually extract from one of the data frames I initially load into the document. Here, I create it manually:

names.icu <- LETTERS[1:3]

When I put this vector into an inline R expression, e.g.

  • Hospital X takes part with the following ICUs: r names.icu.

I get the following output:

“Hospital X takes part with the following ICUs: A, B, C.”

This is not exactly what I want, because by convention the last element of a listing should be preceded by a conjunction (in English: and). While some languages require the next-to-last element of a listing to be followed by a comma, other languages don't. Since the hospitals taking part at the trial have between one and five intensive care units (ICU), I needed to write a function to cover the following cases:

  • If the string vector has one element only, it must not be followed by a conjunction;
  • If the string vector has got more than one element, the next-to-last word of the listing must be followed by:
    • both comma and conjunction or
    • conjunction only.

The add_and() Function

The function I wrote can be found in the following code chunk:

add_and <- function(x, conj = "and") {
  l <- length(x)
  if (l > 1) {
    x[l] = paste(x[l - 1], conj, x[l])
    x = x[-(l - 1)]
    x = sub("\\s,", ",", x)
  }
  else {
    x
  }
  x
}

The function has got two input parameters:

  • x: the character vector and
  • conj: the conjunction (default = “and”) which may be preceded by comma and white space (“, and”) or not (“and”).

First, the function checks, whether the character vector has got more than one element. If not, it is returned as is. If yes, the conjunction is put before the last element of the vector. If the next-to-last listing element is followd by comma, the sub() function (sub("\\s,", ",", x)) removes the white space preceding this comma.

Examples

The following examples show how my function works.

Example 1: Vector with 1 element

  • Hospital X takes part with the following ICU(s): r add_and(names.icu[1]).

returns:

“Hospital X takes part with the following ICU(s): A.”

Example 2: Vector with 3 elements with no conjunction specified

  • Hospital X takes part with the following ICU(s): r add_and(names.icu).

returns:

“Hospital X takes part with the following ICU(s): A, B and C.”

Example 3: Vector with 3 elements with conjunction specified (German)

  • Krankenhaus X nimmt mit den folgenden ITS teil: r add_and(names.icu, 'und').

returns:

“Krankenhaus X nimmt mit den folgenden ITS teil: A, B und C.”

Example 4: Vector with 3 elements with conjunction preceded by comma

  • Hospital X takes part with the following ICU(s): r add_and(names.icu, ', and').

returns:

“Hospital X takes part with the following ICU(s): A, B, and C.”

Nothing New under the Sun

A former Professor of mine sometimes said that reading prevents from discovering “new” things. He was right: A couple of weeks ago, I discovered that the knitr package includes a function (combine_words()) with similar functionality. 🙂

Posted in Tips & Tricks | Tagged | Leave a comment

How to Check if a Date is Within a List of Intervals in R

Intro

I'm currently involved in a research project called EFFECT. EFFECT is a multicentre, cluster-randomised, placebo-controlled cross-over trial evaluating antiseptic body wash of patients on intensive care units (ICU). The trial is to test whether daily antiseptic body wash reduces the risk of intensive care unit (ICU)-acquired primary bacteraemia and ICU-acquired multidrug-resistant organisms. EFFECT requires two types of data: (1) The patients' individual ward-movement history and
(2) microbiological test results (see Meissner 2017).

According to the study protocol, positive blood tests do count as infection unless there is a negative blood test within 48 hours after the positive blood test.

In this blog post, I show how to solve this problem on a computational level.

The Problem

The following code chunk provides an hypothetical example of the microbiological data I have to deal with. The data frame df.mibi contains 4 variables:

  • ID: Patient id (only 1 patient in this example);
  • ORGANISM: name of skin commensal organism found in some blood sample,
  • RESULT: laboratory test result (POS vs. NEG);
  • DATE: date of laboratory test
library(tidyverse)
library(lubridate)

df.mibi <- tibble(
  ID = paste0("ID_", rep(1, 11)),
  ORGANISM = c(rep('Propionibacterium acnes', 2), 
               rep('Staphylococcus epidermidis', 2),
               rep('Staphylococcus capitis', 2),
               rep('', 5)),
  RESULT = c(rep('POS', 6), rep('NEG', 5)),
  DATE = ymd(c(
    "2018-02-07", "2018-02-12", "2018-02-13", "2018-02-20",
    "2018-02-21", "2018-03-18", "2018-02-01", "2018-02-06",
    "2018-02-10", "2018-02-21", "2018-04-05")
  )
)

My Idea

In a first step, I separated df.mibi into two data frames:

  • df.POS: containing positive blood tests only
  • df.NEG: containing negative blood tests only
df.POS <- df.mibi %>%
  filter(RESULT == 'POS')
df.NEG <- df.mibi %>%
  filter(RESULT == 'NEG')

In a second step, I removed two variables from df.NEG (RESULT, ORGANISM), grouped the data frame by ID, and put all dates belonging to one ID into the list column data using the nest() function of the tidyr package

df.NEG <- df.NEG %>%
  select(ID, DATE) %>%
    group_by(ID) %>%
      nest()

This is how both data frames look like:

df.POS
## # A tibble: 6 x 4
##   ID    ORGANISM                   RESULT DATE      
##   <chr> <chr>                      <chr>  <date>    
## 1 ID_1  Propionibacterium acnes    POS    2018-02-07
## 2 ID_1  Propionibacterium acnes    POS    2018-02-12
## 3 ID_1  Staphylococcus epidermidis POS    2018-02-13
## 4 ID_1  Staphylococcus epidermidis POS    2018-02-20
## 5 ID_1  Staphylococcus capitis     POS    2018-02-21
## 6 ID_1  Staphylococcus capitis     POS    2018-03-18
df.NEG
## # A tibble: 1 x 2
##   ID    data            
##   <chr> <list>          
## 1 ID_1  <tibble [5 x 1]>

In a third step, I tried to check whether one of the negative test (stored in the list variable data) lies within the time interval positive test + 48 hours (TIME).
I did the mapping using the map2() function of the purrr package:

# merging and mapping
df.TOTAL <- df.POS %>%
  left_join(df.NEG, by = 'ID') %>%
    mutate(TIME = interval(DATE, DATE + days(2)),
           RESULT = map2(data, "DATE", TIME, ~ .x %within% .y)) 

Unfortunaltely, my code did not work. The RESULT variable should be logical and return TRUE in case of a negative test result up to 2 days after the positive test. Instead it is a list and returns NULL.

df.TOTAL
## # A tibble: 6 x 6
##   ID    ORGANISM   RESULT DATE       data   TIME                          
##   <chr> <chr>      <list> <date>     <list> <S4: Interval>                
## 1 ID_1  Propionib~ <NULL> 2018-02-07 <tibb~ 2018-02-07 UTC--2018-02-09 UTC
## 2 ID_1  Propionib~ <NULL> 2018-02-12 <tibb~ 2018-02-12 UTC--2018-02-14 UTC
## 3 ID_1  Staphyloc~ <NULL> 2018-02-13 <tibb~ 2018-02-13 UTC--2018-02-15 UTC
## 4 ID_1  Staphyloc~ <NULL> 2018-02-20 <tibb~ 2018-02-20 UTC--2018-02-22 UTC
## 5 ID_1  Staphyloc~ <NULL> 2018-02-21 <tibb~ 2018-02-21 UTC--2018-02-23 UTC
## 6 ID_1  Staphyloc~ <NULL> 2018-03-18 <tibb~ 2018-03-18 UTC--2018-03-20 UTC

The Solution

Not even one hour after I posted my question to StackOverflow, a user who calles himself “utubun” found the following solution:

df.TOTAL <- df.POS %>%
  left_join(df.NEG, by = 'ID') %>%
    mutate(TIME = interval(DATE, DATE + days(2)),
           RESULT = map2_lgl(data, TIME, ~ any(.x$DATE %within% .y)))
df.TOTAL
## # A tibble: 6 x 6
##   ID    ORGANISM   RESULT DATE       data   TIME                          
##   <chr> <chr>      <lgl>  <date>     <list> <S4: Interval>                
## 1 ID_1  Propionib~ FALSE  2018-02-07 <tibb~ 2018-02-07 UTC--2018-02-09 UTC
## 2 ID_1  Propionib~ FALSE  2018-02-12 <tibb~ 2018-02-12 UTC--2018-02-14 UTC
## 3 ID_1  Staphyloc~ FALSE  2018-02-13 <tibb~ 2018-02-13 UTC--2018-02-15 UTC
## 4 ID_1  Staphyloc~ TRUE   2018-02-20 <tibb~ 2018-02-20 UTC--2018-02-22 UTC
## 5 ID_1  Staphyloc~ TRUE   2018-02-21 <tibb~ 2018-02-21 UTC--2018-02-23 UTC
## 6 ID_1  Staphyloc~ FALSE  2018-03-18 <tibb~ 2018-03-18 UTC--2018-03-20 UTC

It works!!! Thank you very much! 🙂

Posted in Tips & Tricks | Tagged , , | Leave a comment

Drawing a Fish Curve using R and ggplot2

Intro

Recently, I wondered whether there is a way to draw a fish shape using a mathematical function. Since I did not find a ready-made R function, I tried to write the function by myself. The equations, I've used for writing this function can be found on WolframMathWorld.

The function

The fish_curve() function requires the ggplot2 and the dplyr package. It creates a data frame with two variables (x and y) and 10.000 observations. Finally, the data points are plotted using ggplot2.

fish_curve <- function(colour='black', size = 5){
  library(ggplot2)
  library(dplyr)
  data.frame(
    x = cos(1:10000) - sin(1:10000)^2 / sqrt(2),
    y = cos(1:10000) * sin(1:10000)
  ) %>%
    ggplot(., aes(x, y)) +
    geom_point(colour = colour, size = size) +
    theme_void()
}

Function call with default parameters

With colour and size the fish_curve() function allows the user to specify two parameters; that is colour and size of the plotted points. The default values are black for colour and 5 for size.

(p1 <- fish_curve())

plot of chunk fish-1

Customization

In the following example, we customize colour and size of the fish shape:

(p2 <- fish_curve(colour = 'blue', size = 1))

plot of chunk fish-2

And finally, we place the two plots side by side using the patchwork package:

library(patchwork)
p1 + p2

plot of chunk unnamed-chunk-1

Posted in Tips & Tricks, Visualizing Data | Tagged , | Leave a comment