Tracks
/
R
R
/
Übungen
/
Bäume & Tibble-ationen
Bäume & Tibble-ationen

Bäume & Tibble-ationen

Lernübung

Einführung

In anderen Teilen des Lehrplans haben wir verschiedene Datentypen mit unterschiedlichen Eigenschaften kennengelernt.

  • Atomare Vektoren sind eindimensional und vom Typ her homogen.
  • Listen sind eindimensional, und ihre Elemente können von unterschiedlichem Typ sein.
  • Matrizen und Arrays sind mehrdimensional und homogen.

In diesem Konzept geht es um Möglichkeiten, mehrdimensionale, heterogene Daten zu speichern. In der Praxis sind die meisten Daten aus der realen Welt so aufgebaut, deshalb kommen wir jetzt zum Kern dessen, wie R in der Praxis (überwiegend) verwendet wird.

Dataframe-Varianten

Im Laufe der Jahrzehnte hat R mehrere Datentypen hinzugefügt, um tabellarische Daten zu verarbeiten.

In diesem Lehrplan konzentrieren wir uns hauptsächlich auf Tibbles, aber es ist nützlich, die Alternativen zu kennen.

Das data.frame

In Base R ist ein data.frame eine list aus vectors gleicher Länge. Man kann sich das als rechteckige Datentabelle vorstellen, in der jede Spalte homogen ist, aber jede Zeile unterschiedliche Datentypen enthalten kann (und es meistens auch tut).

Ein Beispiel zur Veranschaulichung:

# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)

# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#>   languages created has.syllabus
#> 1   Fortran    1957        FALSE
#> 2         R    1993         TRUE
#> 3    Python    1991         TRUE
#> 4     Julia    2012         TRUE

Wir haben eine Spalte mit Strings, eine Spalte mit Zahlen und eine Spalte mit booleschen Werten. Wenn man das hochskaliert, ist das eine intuitive Möglichkeit, viele Sammlungen realer Daten darzustellen.

Das tibble

Das Design von data.frame ist alt.

Erfahrungen aus mehreren Jahrzehnten und veränderte Muster in der Nutzung von R führten zu einem Redesign, das im Tidyverse eine modernisierte Alternative hervorbrachte: Tibbles.

Im Vergleich zu Base R bieten Tibbles:

  • Andere Standardeinstellungen, um häufige Probleme zu vermeiden.
  • Weniger Bereitschaft, Datentypen bei der Eingabe umzuwandeln.
  • Mehr und klarere Fehlermeldungen.
  • Andere, meist bessere Anzeigeformate.

Kurz gesagt: Ein tibble will „weniger tun und mehr meckern“ und wird auch als „faul und mürrisch“ beschrieben.

Die Typen sind meist austauschbar: Jede Funktion, die ein data.frame akzeptiert, akzeptiert auch ein tibble, und umgekehrt.

Die Umwandlung zwischen den Typen ist einfach, mit as_tibble(df) und as.data.frame(tbl).

Für neuen Code helfen dir Tibbles wahrscheinlich dabei, robusteren Code zu schreiben. Allerdings gibt es in der R-Welt sehr viel alten Code und alte Daten, deshalb wird das data.frame wahrscheinlich noch lange verbreitet bleiben.

# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
  # A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE      

Beachte das Standardformat der Ausgabe: Die Kommentarzeile mit den Dimensionen wird automatisch ausgegeben, und auch die Spaltentypen werden angezeigt.

Mit Tibbles arbeiten

Tibbles sind ein Kernbestandteil des Tidyverse, also lade sie entweder mit library(tibble) oder library(tidyverse).

Ein Tibble erstellen

Am einfachsten verwenden wir die Funktion tibble(), um Spaltenvektoren zusammenzufügen, genau wie bei data.frame(). Ein Beispiel dafür haben wir im vorherigen Abschnitt gesehen.

Wenn es bequemer ist, Werte zeilenweise einzugeben, ist die passende Funktion tribble().

In der Praxis gibt es Dutzende Möglichkeiten, Tibbles zu erstellen, denn sie sind das Standard-Ausgabeformat vieler verschiedener Tidyverse-Funktionen.

Ein Tibble bearbeiten

Im Konzept zur funktionalen Programmierung ging es um die Bibliothek purrr, mit der sich Vektoren und Listen (eindimensionale Datenstrukturen) bearbeiten lassen.

Für Dataframes (ob traditionell oder als Tibbles) ist die passende Bibliothek dplyr.

Teilmengen bilden

Dataframes, einschließlich Tibbles, lassen sich wie Listen aus Spaltenvektoren behandeln, deshalb liefert die Listen-Indizierung eine bestimmte Spalte zurück.

tbl
# A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE    

tbl$created
#> [1] 1957 1993 1991 2012

Einen Dataframe kannst du auch mit Matrix-artiger Indizierung ansprechen.

tbl[c(2, 4), 1:2]
  # A tibble: 2 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 R            1993
#> 2 Julia        2012

In modernem R mit dem Tidyverse-Ökosystem sind dplyr-Funktionen im Allgemeinen flexibler und bequemer, und um sie geht es im Rest dieses Konzepts.

Spaltenweise Operationen

Eine einzelne Spalte erhältst du mit pull() über den Namen oder die fortlaufende Nummer (negative Zahlen zählen von rechts nach links).

tbl |> pull(created)
#> [1] 1957 1993 1991 2012

Das liefert dasselbe Ergebnis wie tbl$created, nutzt aber eine Funktion, die sich gut in Pipelines einfügt.

Um mehrere Spalten auszuwählen, ist die passende Funktion select(), die sehr vielseitig ist. Damit holst du Spalten anhand von Eigenschaften ihres Namens oder Typs (oder schließt sie aus).

# Range with position and/or name
tbl |> select(1:created)
  # A tibble: 4 × 2
#>   languages created
#>   <chr>       <dbl>
#> 1 Fortran      1957
#> 2 R            1993
#> 3 Python       1991
#> 4 Julia        2012      

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

Mehrere Kriterien sind erlaubt, mit den booleschen Operatoren &, | und ! (und, oder, nicht).

Bei unserem Spielzeug-Dataframe mit Programmiersprachen wirkt diese Macht reichlich albern. Zum Glück ist das starwars-Tibble in dplyr enthalten, womit wir etwas Größeres zum Üben haben.

# pick a subset of columns
starwars |> 
  select(name | ends_with("color")) |> 
  head(5)
  # A tibble: 5 × 4
#>   name           hair_color skin_color  eye_color
#>   <chr>          <chr>      <chr>       <chr>    
#> 1 Luke Skywalker blond      fair        blue     
#> 2 C-3PO          NA         gold        yellow   
#> 3 R2-D2          NA         white, blue red      
#> 4 Darth Vader    none       white       yellow   
#> 5 Leia Organa    brown      light       brown    

Zeilenweise Operationen

Zeilen, die bestimmten Kriterien entsprechen, erhältst du mit filter(), mit filter_out() schließt du sie aus.

starwars |> 
  select(name:mass) |> 
  filter(between(height, 150, 165) & !is.na(mass))
  # A tibble: 4 × 3
#>   name               height  mass
#>   <chr>               <int> <dbl>
#> 1 Leia Organa           150    49
#> 2 Beru Whitesun Lars    165    75
#> 3 Nien Nunb             160    68
#> 4 Ben Quadinaros        163    65

Filterkriterien können beliebig komplex sein, basieren aber immer auf dem Inhalt der Zeilen.

Wenn die Nummern der Zeilen bekannt sind, können wir verschiedene slice()-Funktionen verwenden.

starwars |> 
  select(name | homeworld) |> 
  slice(20:25)
  # A tibble: 6 × 2
#>   name             homeworld
#>   <chr>            <chr>    
#> 1 Palpatine        Naboo    
#> 2 Boba Fett        Kamino   
#> 3 IG-88            NA       
#> 4 Bossk            Trandosha
#> 5 Lando Calrissian Socorro  
#> 6 Lobot            Bespin   

Ein Tibble verändern

Zuerst ein Vorbehalt: Die Standardeinstellung copy-on-modify bedeutet, dass das ursprüngliche Tibble normalerweise unverändert bleibt.

Die meisten Änderungen werden spaltenweise angewendet.

Spaltennamen lassen sich mit rename(newname = oldname) ändern, oder mit rename_with(), um eine Funktion anzuwenden. Achte auf die Syntax innerhalb von rename(). Der Inhalt der Spalte oldname wird an den Namen newname gebunden, daher die Reihenfolge.

Die Spaltenreihenfolge lässt sich mit relocate() ändern. Die angegebenen Spalten werden standardmäßig an die äußerste linke Position verschoben, aber mit einem .before- oder .after-Argument kannst du die Position genauer festlegen.

sw <- starwars |> select(name:species) |> slice(1:4)
sw
  # A tibble: 4 × 11
#>   name           height  mass hair_color skin_color  eye_color birth_year sex   gender    homeworld species
#>   <chr>           <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>     <chr>     <chr>  
#> 1 Luke Skywalker    172    77 blond      fair        blue            19   male  masculine Tatooine  Human  
#> 2 C-3PO             167    75 NA         gold        yellow         112   none  masculine Tatooine  Droid  
#> 3 R2-D2              96    32 NA         white, blue red             33   none  masculine Naboo     Droid  
#> 4 Darth Vader       202   136 none       white       yellow          41.9 male  masculine Tatooine  Human  

sw |> relocate(c(species, homeworld), .after = name)
  # A tibble: 4 × 11
#>   name           species homeworld height  mass hair_color skin_color  eye_color birth_year sex   gender   
#>   <chr>          <chr>   <chr>      <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> <chr>    
#> 1 Luke Skywalker Human   Tatooine     172    77 blond      fair        blue            19   male  masculine
#> 2 C-3PO          Droid   Tatooine     167    75 NA         gold        yellow         112   none  masculine
#> 3 R2-D2          Droid   Naboo         96    32 NA         white, blue red             33   none  masculine
#> 4 Darth Vader    Human   Tatooine     202   136 none       white       yellow          41.9 male  masculine

Für größere Änderungen kannst du mit mutate():

  • neue Spalten erstellen, die Funktionen vorhandener Spalten sind.
  • eine vorhandene Spalte ersetzen, indem du eine neue Spalte mit demselben Namen erstellst.
  • eine Spalte löschen, indem du ihren Wert auf NULL setzt.

mutate() ist eindeutig mächtig, möglicherweise verwirrend und ein Grund, für copy-on-modify sehr dankbar zu sein.

Es gibt keinen offensichtlichen Grund, sich für den Body-Mass-Index von Star-Wars-Figuren zu interessieren, aber nur für den Fall:

starwars |> 
  select(c(name, species, height, mass)) |> 
  mutate(BMI = mass / (height / 100)^2) |> 
  head(4)
  # A tibble: 4 × 5
#>   name           species height  mass   BMI
#>   <chr>          <chr>    <int> <dbl> <dbl>
#> 1 Luke Skywalker Human      172    77  26.0
#> 2 C-3PO          Droid      167    75  26.9
#> 3 R2-D2          Droid       96    32  34.7
#> 4 Darth Vader    Human      202   136  33.3

Zeilenweise Operationen sind beim Verändern einzelner Tibbles weniger üblich (das Zusammenführen mehrerer Tibbles besprechen wir in einem späteren Konzept).

Eine Ausnahme: arrange() sortiert Zeilen nach den Werten in einer oder mehreren Spalten.

tbl
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 R            1993 TRUE        
#> 3 Python       1991 TRUE        
#> 4 Julia        2012 TRUE  

tbl |> arrange(languages)
#>       A tibble: 4 × 3
#>   languages created has.syllabus
#>   <chr>       <dbl> <lgl>       
#> 1 Fortran      1957 FALSE       
#> 2 Julia        2012 TRUE        
#> 3 Python       1991 TRUE        
#> 4 R            1993 TRUE     

Anleitung

Eine große Kirschplantage hat in der Regel einen kleinen Bestand an überschüssigen Bäumen, die sie als Nutzholz anbieten möchte. Das hilft, die Plantage gesund zu halten, und bringt gleichzeitig zusätzliche Einnahmen. Sie haben dich gebeten, bei der Programmierung einer Funktionalität zu helfen, die den Auswahlprozess der Bäume unterstützt, wenn ein potenzieller Kunde anruft.

Note

Die eingebaute datasets-Bibliothek enthält viele Beispiel-Datensätze, und diese Übung verwendet den trees-Datensatz. Um den Datensatz aufzurufen, verwendest du einfach den Namen trees.

library(datasets) # loading is optional

trees |> head(3)
#>    A data.frame: 3 × 3 	
#>   Girth	Height	Volume
#>	 <dbl>	 <dbl>	 <dbl>
#> 1   8.3	    70	  10.3  
#> 2   8.6	    65	  10.3
#> 3   8.8	    63	  10.2

Beachte die drei Spalten: Girth, Height und Volume

Der Datensatz liegt zunächst als data.frame vor, aber du möchtest vielleicht lieber damit arbeiten, als wäre er ein tibble.

1. Benenne eine Spalte um

Leider gab es einen Schreibfehler, der nie korrigiert wurde, und eine Spalte im Datensatz heißt Girth, obwohl es sich eigentlich um den Diameter handelt.

Definiere einen data.frame/tibble namens tree_data, der den Namen der Spalte Girth in trees zu Diameter korrigiert.

head(tree_data, 3)
#>     A tibble: 3 × 3 
#> Diameter	 Height	Volume
#>    <dbl>	  <dbl>	 <dbl>
#>      8.3	     70	  10.3
#>      8.6	     65	  10.3
#>      8.8	     63	  10.2

2. Füge Spalten für Umfang und Gewicht hinzu

Der Umfang (auch Kreisumfang genannt) ist nach wie vor ein nützliches Maß, und das Gewicht des Holzes ebenfalls. Der Umfang lässt sich leicht mit πD berechnen, wobei D der Durchmesser ist. Das Gewicht des gesamten Holzes lässt sich mit ρV berechnen, wobei ρ = 35 die Dichte von Kirschholz ist und V das Volumen des Holzes.

Definiere die Funktion girth_n_weight(data, rnd_digits), die einen DataFrame und numerische Argumente entgegennimmt. Die Funktion soll einen neuen DataFrame mit den beiden zusätzlichen Spalten Girth und Weight zurückgeben, gerundet auf die entsprechende Anzahl von Nachkommastellen, die durch rnd_digits vorgegeben wird.

girth_n_weight(tree_data, 1) |> head(3)
#>           A tibble: 3 × 5 
#> Diameter  Height	 Volume	 Girth	Weight
#>    <dbl>	  <dbl>	  <dbl>	 <dbl>	 <dbl>
#>      8.3	     70	   10.3	  26.1	 360.5
#>      8.6	     65	   10.3	  27.0	 360.5
#>      8.8	     63	   10.2	  27.6	 357.0

3. Plantagenkopie des Datensatzes

Für jeden potenziellen Kunden legt die Plantage eine spezielle Kopie des Datensatzes an, um den Verkauf zu unterstützen. Bei diesem Datensatz werden die wichtigsten Spalten Weight und Height nach vorne verschoben, und er wird nach Weight sortiert.

Definiere die Funktion orchard_copy(data, important_cols), die einen DataFrame als Eingabe entgegennimmt. Diese soll einen neuen DataFrame zurückgeben, bei dem Weight und Height nach vorne verschoben und die Zeilen nach Weight sortiert sind.

tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#>            A tibble: 3 × 5 
#> Weight	Height	Diameter	Volume	Girth
#>  <dbl>	 <dbl>	   <dbl>	 <dbl>	<dbl>
#>    357	    63	       9	    10	   28
#>    360	    70	       8	    10	   26
#>    360	    65	       9	    10	   27

4. Kundenkopie des Datensatzes

Jeder potenzielle Kunde bekommt eine persönliche Version des Datensatzes, die auf seinen Vorlieben basiert. Dazu kann der Kunde Folgendes angeben: die minimale Höhe, die maximale Höhe und das maximale Gewicht eines einzelnen Baums.

Definiere die Funktion customer_copy(data, min_height, max_height, max_weight). Gib eine Kundenkopie des DataFrames mit den Spalten Height, Weight, Diameter und Girth zurück, die nur die Zeilen enthält, die innerhalb der Vorgaben liegen.

tree_data |> 
    girth_n_weight(1) |> 
    orchard_copy() |>
    customer_copy(65, 75, 1500) |> 
    head(3)
#>        A tibble: 3 × 4 
#> Height	Weight	Diameter	Girth
#>  <dbl>	 <dbl>	   <dbl>	<dbl>
#>     70	 360.5	     8.3	 26.1
#>     65	 360.5	     8.6	 27.0
#>     66	 546.0	    11.0	 34.6
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
R Exercism

Bereit, mit Bäume & Tibble-ationen zu starten?

Melde dich bei Exercism an, um R mit 21 Konzepte111 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.