Relational data

Pendahuluan

Pada proses analisis data, kita jarang melibatkan hanya satu tabel data. Biasanya kita memiliki banyak tabel data untuk kita proses sesuai dengan tujuan kita, dan kita harus menggabungkannya dua atau tiga atau lebih untuk menjawab pertanyaan yang kita miliki. Secara kolektif, secara umum beberapa tabel data yang tergabung pada satu kesatuan disebut sebagai data relasional karena relasinya, bukan hanya kumpulan data individual, yang dianggap penting.

Relasi selalu ditentukan antara sepasang tabel / dua tabel. Semua relasi lainnya dibangun dari ide sederhana ini: relasi dari tiga tabel atau lebih selalu merupakan properti relasi antara setiap pasangan. Terkadang kedua elemen pasangan bisa menjadi tabel yang sama! Ini diperlukan jika, misalnya, jika kita memilki suatu data yang teridiri dari beberapa tabel, dan setiap tabel memiliki referensi ke parent nya.

Perintah dalam dplyr (dibandingkan dengan perintah pada SQL) adalah sebagai berikut :

Fungsi dalam dplyr Fungsi dalam SQL Keterangan
select() SELECT Menyeleksi kolom variabel
filter() WHERE Menyaring (filter) baris
group_by() GROUP_BY Mengelompokkan data
summarise() tidak ada Merangkum data
arrange() ORDER_BY Mengurutkan data
mutate() COLUMN ALIAS Membuat kolom baru
join() JOIN Menggabungkan data frame

Untuk bekerja dengan data relasional, kita memerlukan kata kerja yang bekerja dengan pasangan tabel. Ada tiga kelompok kata kerja yang dirancang untuk bekerja dengan data relasional:

1. Mutasi gabungan, yang menambahkan variabel baru ke satu bingkai data dari pengamatan yang cocok di yang lain.

2. Gabungan pemfilteran, yang memfilter pengamatan dari satu bingkai data berdasarkan apakah mereka cocok atau tidak dengan pengamatan di tabel lain.

3. Operasi set, yang memperlakukan observasi seolah-olah itu adalah elemen yang ditetapkan.

Tempat paling umum untuk menemukan data relasional adalah di sistem manajemen basis data relasional (atau RDBMS), istilah yang mencakup hampir semua basis data modern. Jika Anda pernah menggunakan database sebelumnya, Anda hampir pasti pernah menggunakan SQL. Jika demikian, Anda akan menemukan konsep dalam bab ini sudah tidak asing lagi, meskipun ekspresi mereka dalam dplyr sedikit berbeda. Secara umum, dplyr sedikit lebih mudah digunakan daripada SQL karena dplyr dikhususkan untuk melakukan analisis data: dplyr membuat operasi analisis data umum menjadi lebih mudah, maka kita bisa mempermudah untuk melakukan hal-hal lain yang biasanya diperlukan untuk analisis data pada seorang data sains.

Prasyarat

Kami akan mengeksplorasi data relasional dari nycflights13 menggunakan library dplyr.

Operator pipe: %>%

Pipe operator: %>% dalam dplyr diimport dari package magrittr. Pipe digunakan untuk menyalurkan output dari suatu fungsi ke input fungsi lain. Alih-alih menggunakan fungsi tersarang, digunakanlah pipe yang dibaca dari kiri ke kanan. Contoh:

Lagkah 2

library(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
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2     ✓ purrr   0.3.4
## ✓ tibble  3.0.4     ✓ stringr 1.4.0
## ✓ tidyr   1.1.2     ✓ forcats 0.5.0
## ✓ readr   1.4.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(nycflights13)
library(ggplot2)
library(maps)
## 
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
## 
##     map

Maskapai penerbangan di ambil dari data set airlines, kita memungkinkan untuk mencari nama lengkap maskapai dan fungsi str() kita bisa mengetahui struktur dari data set airlines.

Lagkah 3

str(airlines)
## tibble [16 × 2] (S3: tbl_df/tbl/data.frame)
##  $ carrier: chr [1:16] "9E" "AA" "AS" "B6" ...
##  $ name   : chr [1:16] "Endeavor Air Inc." "American Airlines Inc." "Alaska Airlines Inc." "JetBlue Airways" ...
airlines
## # A tibble: 16 x 2
##    carrier name                       
##    <chr>   <chr>                      
##  1 9E      Endeavor Air Inc.          
##  2 AA      American Airlines Inc.     
##  3 AS      Alaska Airlines Inc.       
##  4 B6      JetBlue Airways            
##  5 DL      Delta Air Lines Inc.       
##  6 EV      ExpressJet Airlines Inc.   
##  7 F9      Frontier Airlines Inc.     
##  8 FL      AirTran Airways Corporation
##  9 HA      Hawaiian Airlines Inc.     
## 10 MQ      Envoy Air                  
## 11 OO      SkyWest Airlines Inc.      
## 12 UA      United Air Lines Inc.      
## 13 US      US Airways Inc.            
## 14 VX      Virgin America             
## 15 WN      Southwest Airlines Co.     
## 16 YV      Mesa Airlines Inc.

Bandara di ambil dari data set airports, kita memungkinkan untuk mencari nama lengkap bandara dan fungsi str() kita bisa mengetahui struktur dari data set bandara.

Lagkah 4

str(airports)
## tibble [1,458 × 8] (S3: tbl_df/tbl/data.frame)
##  $ faa  : chr [1:1458] "04G" "06A" "06C" "06N" ...
##  $ name : chr [1:1458] "Lansdowne Airport" "Moton Field Municipal Airport" "Schaumburg Regional" "Randall Airport" ...
##  $ lat  : num [1:1458] 41.1 32.5 42 41.4 31.1 ...
##  $ lon  : num [1:1458] -80.6 -85.7 -88.1 -74.4 -81.4 ...
##  $ alt  : num [1:1458] 1044 264 801 523 11 ...
##  $ tz   : num [1:1458] -5 -6 -6 -5 -5 -5 -5 -5 -5 -8 ...
##  $ dst  : chr [1:1458] "A" "A" "A" "A" ...
##  $ tzone: chr [1:1458] "America/New_York" "America/Chicago" "America/Chicago" "America/New_York" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   name = col_character(),
##   ..   city = col_character(),
##   ..   country = col_character(),
##   ..   faa = col_character(),
##   ..   icao = col_character(),
##   ..   lat = col_double(),
##   ..   lon = col_double(),
##   ..   alt = col_double(),
##   ..   tz = col_double(),
##   ..   dst = col_character(),
##   ..   tzone = col_character()
##   .. )
airports
## # A tibble: 1,458 x 8
##    faa   name                       lat    lon   alt    tz dst   tzone          
##    <chr> <chr>                    <dbl>  <dbl> <dbl> <dbl> <chr> <chr>          
##  1 04G   Lansdowne Airport         41.1  -80.6  1044    -5 A     America/New_Yo…
##  2 06A   Moton Field Municipal A…  32.5  -85.7   264    -6 A     America/Chicago
##  3 06C   Schaumburg Regional       42.0  -88.1   801    -6 A     America/Chicago
##  4 06N   Randall Airport           41.4  -74.4   523    -5 A     America/New_Yo…
##  5 09J   Jekyll Island Airport     31.1  -81.4    11    -5 A     America/New_Yo…
##  6 0A9   Elizabethton Municipal …  36.4  -82.2  1593    -5 A     America/New_Yo…
##  7 0G6   Williams County Airport   41.5  -84.5   730    -5 A     America/New_Yo…
##  8 0G7   Finger Lakes Regional A…  42.9  -76.8   492    -5 A     America/New_Yo…
##  9 0P2   Shoestring Aviation Air…  39.8  -76.6  1000    -5 U     America/New_Yo…
## 10 0S9   Jefferson County Intl     48.1 -123.    108    -8 A     America/Los_An…
## # … with 1,448 more rows

Pesawat terbang di ambil dari data set planes, kita memungkinkan untuk mencari nama lengkap planes dan fungsi str() kita bisa mengetahui struktur dari data set planes.

Lagkah 5

str(planes)
## tibble [3,322 × 9] (S3: tbl_df/tbl/data.frame)
##  $ tailnum     : chr [1:3322] "N10156" "N102UW" "N103US" "N104UW" ...
##  $ year        : int [1:3322] 2004 1998 1999 1999 2002 1999 1999 1999 1999 1999 ...
##  $ type        : chr [1:3322] "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" ...
##  $ manufacturer: chr [1:3322] "EMBRAER" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" ...
##  $ model       : chr [1:3322] "EMB-145XR" "A320-214" "A320-214" "A320-214" ...
##  $ engines     : int [1:3322] 2 2 2 2 2 2 2 2 2 2 ...
##  $ seats       : int [1:3322] 55 182 182 182 55 182 182 182 182 182 ...
##  $ speed       : int [1:3322] NA NA NA NA NA NA NA NA NA NA ...
##  $ engine      : chr [1:3322] "Turbo-fan" "Turbo-fan" "Turbo-fan" "Turbo-fan" ...
planes
## # A tibble: 3,322 x 9
##    tailnum  year type          manufacturer   model  engines seats speed engine 
##    <chr>   <int> <chr>         <chr>          <chr>    <int> <int> <int> <chr>  
##  1 N10156   2004 Fixed wing m… EMBRAER        EMB-1…       2    55    NA Turbo-…
##  2 N102UW   1998 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  3 N103US   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  4 N104UW   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  5 N10575   2002 Fixed wing m… EMBRAER        EMB-1…       2    55    NA Turbo-…
##  6 N105UW   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  7 N107US   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  8 N108UW   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
##  9 N109UW   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
## 10 N110UW   1999 Fixed wing m… AIRBUS INDUST… A320-…       2   182    NA Turbo-…
## # … with 3,312 more rows

Menggunakan select()

Lakukan seleksi terhadap kolom dengan sintak: select(data, …), isikan … dengan nama variabel yang ingin dipilih.

Lagkah 6

planes.se <- dplyr::select(planes, tailnum:manufacturer)
head(planes.se,n=5)
## # A tibble: 5 x 4
##   tailnum  year type                    manufacturer    
##   <chr>   <int> <chr>                   <chr>           
## 1 N10156   2004 Fixed wing multi engine EMBRAER         
## 2 N102UW   1998 Fixed wing multi engine AIRBUS INDUSTRIE
## 3 N103US   1999 Fixed wing multi engine AIRBUS INDUSTRIE
## 4 N104UW   1999 Fixed wing multi engine AIRBUS INDUSTRIE
## 5 N10575   2002 Fixed wing multi engine EMBRAER

Beberapa fungsi yang dapat digunakan dalam select():

1. ends_with() : menyeleksi kolom dengan akhiran suatu karakter string

2. contains() : menyeleksi kolom yang mengandung suatu karakter string

3. matches() : menyeleksi kolom yang cocok dengan ekspresi

4. one_of() : menyeleksi nama kolom dari sekelompok/grup nama

Kondisi cuaca di ambil dari data set weather, kita memungkinkan untuk mencari kondisi lengkap cuaca dan fungsi str() kita bisa mengetahui struktur dari data set weather.

Lagkah 7

str(weather)
## tibble [26,115 × 15] (S3: tbl_df/tbl/data.frame)
##  $ origin    : chr [1:26115] "EWR" "EWR" "EWR" "EWR" ...
##  $ year      : int [1:26115] 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
##  $ month     : int [1:26115] 1 1 1 1 1 1 1 1 1 1 ...
##  $ day       : int [1:26115] 1 1 1 1 1 1 1 1 1 1 ...
##  $ hour      : int [1:26115] 1 2 3 4 5 6 7 8 9 10 ...
##  $ temp      : num [1:26115] 39 39 39 39.9 39 ...
##  $ dewp      : num [1:26115] 26.1 27 28 28 28 ...
##  $ humid     : num [1:26115] 59.4 61.6 64.4 62.2 64.4 ...
##  $ wind_dir  : num [1:26115] 270 250 240 250 260 240 240 250 260 260 ...
##  $ wind_speed: num [1:26115] 10.36 8.06 11.51 12.66 12.66 ...
##  $ wind_gust : num [1:26115] NA NA NA NA NA NA NA NA NA NA ...
##  $ precip    : num [1:26115] 0 0 0 0 0 0 0 0 0 0 ...
##  $ pressure  : num [1:26115] 1012 1012 1012 1012 1012 ...
##  $ visib     : num [1:26115] 10 10 10 10 10 10 10 10 10 10 ...
##  $ time_hour : POSIXct[1:26115], format: "2013-01-01 01:00:00" "2013-01-01 02:00:00" ...
weather
## # A tibble: 26,115 x 15
##    origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
##    <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
##  1 EWR     2013     1     1     1  39.0  26.1  59.4      270      10.4 
##  2 EWR     2013     1     1     2  39.0  27.0  61.6      250       8.06
##  3 EWR     2013     1     1     3  39.0  28.0  64.4      240      11.5 
##  4 EWR     2013     1     1     4  39.9  28.0  62.2      250      12.7 
##  5 EWR     2013     1     1     5  39.0  28.0  64.4      260      12.7 
##  6 EWR     2013     1     1     6  37.9  28.0  67.2      240      11.5 
##  7 EWR     2013     1     1     7  39.0  28.0  64.4      240      15.0 
##  8 EWR     2013     1     1     8  39.9  28.0  62.2      250      10.4 
##  9 EWR     2013     1     1     9  39.9  28.0  62.2      260      15.0 
## 10 EWR     2013     1     1    10  41    28.0  59.6      260      13.8 
## # … with 26,105 more rows, and 5 more variables: wind_gust <dbl>, precip <dbl>,
## #   pressure <dbl>, visib <dbl>, time_hour <dttm>

Menyeleksi baris dengan filter()

Lakukan seleksi weather yang temperaturnya lebir dari sama dengan 40 :

Lagkah 8

weather.se <- dplyr::filter(weather, temp >= 40)
print(weather.se)
## # A tibble: 19,401 x 15
##    origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
##    <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
##  1 EWR     2013     1     1    10  41    28.0  59.6      260      13.8 
##  2 EWR     2013     1     1    11  41    27.0  57.1      260      15.0 
##  3 EWR     2013     1     5    10  41    21.0  44.5      310      10.4 
##  4 EWR     2013     1     5    11  42.1  19.9  40.8      320      15.0 
##  5 EWR     2013     1     5    12  43.0  19.9  39.4      310      13.8 
##  6 EWR     2013     1     5    13  44.1  19.9  37.8      290      11.5 
##  7 EWR     2013     1     5    14  44.1  19.9  37.8      310       9.21
##  8 EWR     2013     1     5    15  43.0  19.0  37.9      310       8.06
##  9 EWR     2013     1     5    16  41    19.9  42.5      310       4.60
## 10 EWR     2013     1     6    11  43.0  30.0  59.9      240      10.4 
## # … with 19,391 more rows, and 5 more variables: wind_gust <dbl>, precip <dbl>,
## #   pressure <dbl>, visib <dbl>, time_hour <dttm>

Dapat pula digunakan operasi logika: < (kurang dari), > (lebih dari), <= (kurang dari sama dengan), >= (lebih dari sama dengan), == (sama dengan), != (tidak sama dengan), %in% (dalam).

Operator pipe: %>%

Pipe operator: %>% dalam dplyr diimport dari package magrittr. Pipe digunakan untuk menyalurkan output dari suatu fungsi ke input fungsi lain. Alih-alih menggunakan fungsi tersarang, digunakanlah pipe yang dibaca dari kiri ke kanan. Contoh:

Lagkah 9

head(select(weather, origin, year, temp), n = 5)
## # A tibble: 5 x 3
##   origin  year  temp
##   <chr>  <int> <dbl>
## 1 EWR     2013  39.0
## 2 EWR     2013  39.0
## 3 EWR     2013  39.0
## 4 EWR     2013  39.9
## 5 EWR     2013  39.0

dengan menggunakan pipe, dapat dituliskan sebagai berikut :

weather %>%
  select(origin, year, temp) %>%
  head(n = 5)
## # A tibble: 5 x 3
##   origin  year  temp
##   <chr>  <int> <dbl>
## 1 EWR     2013  39.0
## 2 EWR     2013  39.0
## 3 EWR     2013  39.0
## 4 EWR     2013  39.9
## 5 EWR     2013  39.0

Menyusun menggunakan arrage()

Untuk mengurutkan suatu kolom, gunakan arrange(). Urutkan data weather, berdasarkan variabel temperature :

Lagkah 10

weather.ar <- arrange(weather, temp)
head(weather.ar , n = 10)
## # A tibble: 10 x 15
##    origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
##    <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
##  1 EWR     2013     1    23     5  10.9 -4     50.2      270      10.4 
##  2 EWR     2013     1    23     6  10.9 -4     50.2      270      11.5 
##  3 EWR     2013     1    23     2  12.0 -7.06  41.3      270      10.4 
##  4 EWR     2013     1    23     3  12.0 -5.98  43.5      270       8.06
##  5 EWR     2013     1    23     4  12.0 -5.08  45.4      270       9.21
##  6 EWR     2013     1    23     7  12.0 -2.92  50.4      270      11.5 
##  7 EWR     2013     1    24     3  12.0  1.94  63.3       30       4.60
##  8 JFK     2013     1    23     4  12.0 -7.06  41.3      280      12.7 
##  9 JFK     2013     1    23     5  12.0 -5.08  45.4      280      15.0 
## 10 JFK     2013     1    23     6  12.0 -5.08  45.4      290      13.8 
## # … with 5 more variables: wind_gust <dbl>, precip <dbl>, pressure <dbl>,
## #   visib <dbl>, time_hour <dttm>

Gunakan desc() untuk mengurutkan dari yang terbesar ke yang terkecil:

Lagkah 11

weather.ab <- arrange(weather, desc(temp))
head(weather.ab , n = 10)
## # A tibble: 10 x 15
##    origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
##    <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
##  1 EWR     2013     7    18    15 100.   66.0  33.2      300       9.21
##  2 EWR     2013     7    19    16 100.   71.1  39.5      230      20.7 
##  3 EWR     2013     7    19    13  99.0  72.0  42.1      260      18.4 
##  4 EWR     2013     7    19    14  99.0  71.1  40.8      250      20.7 
##  5 EWR     2013     7    19    15  99.0  72.0  42.1      240      16.1 
##  6 LGA     2013     7    18    15  99.0  64.0  32.0      250      12.7 
##  7 LGA     2013     7    19    15  99.0  69.1  38.2      240      16.1 
##  8 LGA     2013     7    19    16  99.0  69.1  38.2      230      15.0 
##  9 EWR     2013     7    18    12  98.1  69.1  39.2      300       9.21
## 10 EWR     2013     7    18    13  98.1  66.9  36.4      300      11.5 
## # … with 5 more variables: wind_gust <dbl>, precip <dbl>, pressure <dbl>,
## #   visib <dbl>, time_hour <dttm>

Membuat kolom dengan mutate()

Buat kolom weather_proportion yang merupakan rasio temp terhadap dewp :

Lagkah 12

weather %>%
  mutate(weather_proportion = temp/dewp) %>%
  head(5)
## # A tibble: 5 x 16
##   origin  year month   day  hour  temp  dewp humid wind_dir wind_speed wind_gust
##   <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>     <dbl>
## 1 EWR     2013     1     1     1  39.0  26.1  59.4      270      10.4         NA
## 2 EWR     2013     1     1     2  39.0  27.0  61.6      250       8.06        NA
## 3 EWR     2013     1     1     3  39.0  28.0  64.4      240      11.5         NA
## 4 EWR     2013     1     1     4  39.9  28.0  62.2      250      12.7         NA
## 5 EWR     2013     1     1     5  39.0  28.0  64.4      260      12.7         NA
## # … with 5 more variables: precip <dbl>, pressure <dbl>, visib <dbl>,
## #   time_hour <dttm>, weather_proportion <dbl>

Membuat rangkuman data frame menggunakan summarise()

Untuk melakukan perhitungan statistika dalam suatu kolom, gunakan summarise(). Beberapa fungsi statistika sederhana yang dapat digunakan adalah mean(), median(), sd(), min(), max(), n() untuk mengetahui panjang dari vektor, n_distinct() untuk mengetahui jumlah nilai yang berbeda dalam suatu vektor, first() mengetahui nilai pertama suatu vektor, dan last() mengetahui nilai terakhir suatu vektor.

Tampilkan nilai rata-rata, median, minimum, dan maximum dari variabel temp :

Lagkah 13

weather %>%
  summarise(rata2 = mean(dewp), median = median(dewp), 
            minimum = min(dewp), maximum = max(dewp))
## # A tibble: 1 x 4
##   rata2 median minimum maximum
##   <dbl>  <dbl>   <dbl>   <dbl>
## 1    NA     NA      NA      NA

Mengelompokkan operasi dengan group_by()

Fungsi group_by() berhubungan dengan konsep “split-apply-combine”. Misalkan, kita menginginkan untuk memisah (split) data frame menggunakan suatu variabel (contoh order taksonomi), mengaplikasikan (apply) fungsi secara individual dalam data frame, dan mengkombinasi (combine) outputnya.

Pisahkan (split) data frame berdasarkan variabel order, kemudian tampilan ringkasan statistika seperti sebelumnya:

Lagkah 14

weather %>%
  group_by(month) %>%
 summarise(rata2 = mean(dewp), median = median(dewp), 
            minimum = min(dewp), maximum = max(dewp))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 12 x 5
##    month rata2 median minimum maximum
##    <int> <dbl>  <dbl>   <dbl>   <dbl>
##  1     1  22.3   24.1   -9.94    59  
##  2     2  21.6   23     -2.92    46.9
##  3     3  24.8   25.0    1.04    57.2
##  4     4  34.3   37.0    1.04    63.0
##  5     5  48.5   48.9   12.0     69.8
##  6     6  59.5   61.0   34.0     73.9
##  7     7  67.0   69.1   43.0     78.1
##  8     8  NA     NA     NA       NA  
##  9     9  53.2   52.0   32       75.0
## 10    10  46.6   48.0   19.0     72.0
## 11    11  28.8   26.1   -7.96    63.0
## 12    12  27.7   27.0   -0.04    62.6

Salah satu cara untuk menunjukkan hubungan antara tabel yang berbeda adalah dapat ditunjukkan dengan diagram sebagai berikut :

Gambar Relational antar 5 data

Diagram pada gambar realitional tabel seperti diatas, tetapi sederhana dibandingkan dengan diagram yang akan kita lihat di pada kenyataan yang kita hadapi, untuk memahami diagram seperti diatas dengan mengingat setiap relasi selalu menyangkut sepasang tabel. Kita tidak perlu memahami semuanya; kita hanya perlu memahami rantai hubungan antar tabel yang kita miliki.

Untuk tabel nycflights13 :

1. flights terhubung dengan planes melalui satu variable, tailnum.

2. flights terhubung dengan airlines melalui variable pembawa.

3. flights terhubung dengan airports pada dua cara : yaitu melalui the origin and dest variables.

4. flights terhubung dengan weather melalui origin (the location), dan year, month, day dan hour (the time).

Kunci

Variabel yang digunakan untuk menghubungkan setiap pasangan tabel dikatakan sebagai kunci. Kunci adalah variabel (atau kumpulan variabel) yang secara unik mengidentifikasi observasi. Dalam kasus sederhana, satu variabel sudah cukup untuk mengidentifikasi observasi. Misalnya, setiap bidang diidentifikasi secara unik oleh tailnumnya. Dalam kasus lain, beberapa variabel mungkin diperlukan. Misalnya, untuk mengidentifikasi pengamatan cuaca, kita memerlukan lima variabel: tahun, bulan, hari, jam, dan asal.

Ada dua jenis kunci:

1. A primary key / Kunci utama adalah variabel yang unik untuk mengidentifikasi observasi di tabelnya sendiri. Misalnya, planes$tailnum adalah kunci utama karena kunci ini secara unik mengidentifikasi setiap bidang di tabel plane.

2. A foreign key / Kunci asing adalah variabel yang unik untuk mengidentifikasi observasi di tabel lain. Misalnya, flight$tailnum adalah kunci asing karena muncul di tabel flight yang mencocokkan setiap flight dengan plane.

Sebuah variabel dapat berupa kunci utama dan kunci asing. Misalnya, origin adalah bagian dari kunci utama weather, dan juga kunci asing untuk tabel airport.

Setelah kita mengidentifikasi kunci utama di tabel, praktik yang baik adalah memverifikasi bahwa kunci tersebut memang mengidentifikasi setiap pengamatan secara unik. Salah satu cara untuk melakukannya adalah dengan fungsi count() pada kunci utama dan mencari entri di mana n lebih besar dari satu:

Lagkah 15

planes %>% 
  count(tailnum) %>% 
  filter(n > 1)
## # A tibble: 0 x 2
## # … with 2 variables: tailnum <chr>, n <int>
weather %>% 
  count(year, month, day, hour, origin) %>% 
  filter(n > 1)
## # A tibble: 3 x 6
##    year month   day  hour origin     n
##   <int> <int> <int> <int> <chr>  <int>
## 1  2013    11     3     1 EWR        2
## 2  2013    11     3     1 JFK        2
## 3  2013    11     3     1 LGA        2

Terkadang tabel tidak memiliki kunci utama secara eksplisit: dimana setiap baris dapat dikatakan sebagai pengamatan, tetapi tidak ada kombinasi variabel yang dapat diandalkan untuk mengidentifikasi sebagai kunci utama. Misalnya, apa kunci utama di tabel flights ? Kita mungkin dapat mengira bahwa kata kunci adalah date dan tailnum , tetapi tidak ada yang unik:

Lagkah 16

flights
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
##  1  2013     1     1      517            515         2      830            819
##  2  2013     1     1      533            529         4      850            830
##  3  2013     1     1      542            540         2      923            850
##  4  2013     1     1      544            545        -1     1004           1022
##  5  2013     1     1      554            600        -6      812            837
##  6  2013     1     1      554            558        -4      740            728
##  7  2013     1     1      555            600        -5      913            854
##  8  2013     1     1      557            600        -3      709            723
##  9  2013     1     1      557            600        -3      838            846
## 10  2013     1     1      558            600        -2      753            745
## # … with 336,766 more rows, and 11 more variables: arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>

Lagkah 17

flights %>% 
  count(year, month, day, flight) %>% 
  filter(n > 1)
## # A tibble: 29,768 x 5
##     year month   day flight     n
##    <int> <int> <int>  <int> <int>
##  1  2013     1     1      1     2
##  2  2013     1     1      3     2
##  3  2013     1     1      4     2
##  4  2013     1     1     11     3
##  5  2013     1     1     15     2
##  6  2013     1     1     21     2
##  7  2013     1     1     27     4
##  8  2013     1     1     31     2
##  9  2013     1     1     32     2
## 10  2013     1     1     35     2
## # … with 29,758 more rows
flights %>% 
  count(year, month, day, tailnum) %>% 
  filter(n > 1)
## # A tibble: 64,928 x 5
##     year month   day tailnum     n
##    <int> <int> <int> <chr>   <int>
##  1  2013     1     1 N0EGMQ      2
##  2  2013     1     1 N11189      2
##  3  2013     1     1 N11536      2
##  4  2013     1     1 N11544      3
##  5  2013     1     1 N11551      2
##  6  2013     1     1 N12540      2
##  7  2013     1     1 N12567      2
##  8  2013     1     1 N13123      2
##  9  2013     1     1 N13538      3
## 10  2013     1     1 N13566      3
## # … with 64,918 more rows

Saat mulai bekerja dengan data set ini, kita bisa berasumsi bahwa setiap flight number hanya akan digunakan sekali per hari: itu akan membuatnya lebih mudah untuk mengkomunikasikan masalah dengan flight tertentu. Sayangnya, itu bukan masalahnya! Jika tabel tidak memiliki kunci utama, terkadang berguna untuk menambahkannya dengan fungsi mutate() dan fungsi row_number(). Itu membuatnya lebih mudah untuk mencocokkan pengamatan jika kita telah melakukan beberapa pemfilteran dan ingin memeriksa kembali dengan data asli. Ini dapat disebut sebagai kunci pengganti.

Kunci utama dan kunci asing terkait di tabel lain akan membentuk relasi. Hubungan biasanya satu-ke-banyak. Misalnya setiap penerbangan memiliki satu pesawat, tetapi setiap pesawat memiliki banyak penerbangan. Di data lain, kita terkadang akan melihat hubungan 1-dengan-1. Kita dapat menganggap ini sebagai kasus khusus yaitu 1-ke-banyak. Kita dapat memodelkan relasi banyak-ke-banyak dengan relasi banyak-ke-1 ditambah relasi 1-ke-banyak. Misalnya, dalam data ini ada hubungan banyak ke banyak antara airlines dan airports: setiap airline ke banyak airports; setiap airport menampung banyak airlines.

Mutating joins

Alat pertama yang akan kita lihat untuk menggabungkan sepasang tabel adalah mutating join. Mutating join memungkinkan kita menggabungkan variabel dari dua tabel. Ini pertama kali mencocokkan observasi dengan kuncinya, kemudian menyalin seluruh variabel dari satu tabel ke tabel lainnya.

Seperti fungsi mutate(), fungsi gabungan menambahkan variabel ke kanan, jadi jika kita sudah memiliki banyak variabel, variabel baru tidak akan dicetak. Untuk contohnya seperti ini, kami akan mempermudah untuk melihat apa yang terjadi di contoh dengan membuat kumpulan data yang lebih sempit:

Lagkah 18

flights2 <- flights %>% 
  select(year:day, hour, origin, dest, tailnum, carrier)
flights2
## # A tibble: 336,776 x 8
##     year month   day  hour origin dest  tailnum carrier
##    <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>  
##  1  2013     1     1     5 EWR    IAH   N14228  UA     
##  2  2013     1     1     5 LGA    IAH   N24211  UA     
##  3  2013     1     1     5 JFK    MIA   N619AA  AA     
##  4  2013     1     1     5 JFK    BQN   N804JB  B6     
##  5  2013     1     1     6 LGA    ATL   N668DN  DL     
##  6  2013     1     1     5 EWR    ORD   N39463  UA     
##  7  2013     1     1     6 EWR    FLL   N516JB  B6     
##  8  2013     1     1     6 LGA    IAD   N829AS  EV     
##  9  2013     1     1     6 JFK    MCO   N593JB  B6     
## 10  2013     1     1     6 LGA    ORD   N3ALAA  AA     
## # … with 336,766 more rows

(Ingat, saat kita berada di RStudio, kita juga dapat menggunakan fyngsi View() untuk menghindari masalah ini.)

Bayangkan kita ingin menambahkan nama lengkap maskapai penerbangan ke data flights2. Kita dapat menggabungkan bingkai data maskapai dan penerbangan2 dengan left_join ():

Lagkah 20

flights2 %>%
  select(-origin, -dest) %>% 
  left_join(airlines, by = "carrier")
## # A tibble: 336,776 x 7
##     year month   day  hour tailnum carrier name                    
##    <int> <int> <int> <dbl> <chr>   <chr>   <chr>                   
##  1  2013     1     1     5 N14228  UA      United Air Lines Inc.   
##  2  2013     1     1     5 N24211  UA      United Air Lines Inc.   
##  3  2013     1     1     5 N619AA  AA      American Airlines Inc.  
##  4  2013     1     1     5 N804JB  B6      JetBlue Airways         
##  5  2013     1     1     6 N668DN  DL      Delta Air Lines Inc.    
##  6  2013     1     1     5 N39463  UA      United Air Lines Inc.   
##  7  2013     1     1     6 N516JB  B6      JetBlue Airways         
##  8  2013     1     1     6 N829AS  EV      ExpressJet Airlines Inc.
##  9  2013     1     1     6 N593JB  B6      JetBlue Airways         
## 10  2013     1     1     6 N3ALAA  AA      American Airlines Inc.  
## # … with 336,766 more rows

Hasil dari bergabungnya airlines ke flights2 adalah variabel tambahan: name. Inilah mengapa kita menyebut jenis join ini sebagai mutating join. Dalam kasus ini, kita bisa mendapatkan tempat yang sama menggunakan mutate() dan subset dasar R.

Lagkah 21

flights2 %>%
  select(-origin, -dest) %>% 
  mutate(name = airlines$name[match(carrier, airlines$carrier)])
## # A tibble: 336,776 x 7
##     year month   day  hour tailnum carrier name                    
##    <int> <int> <int> <dbl> <chr>   <chr>   <chr>                   
##  1  2013     1     1     5 N14228  UA      United Air Lines Inc.   
##  2  2013     1     1     5 N24211  UA      United Air Lines Inc.   
##  3  2013     1     1     5 N619AA  AA      American Airlines Inc.  
##  4  2013     1     1     5 N804JB  B6      JetBlue Airways         
##  5  2013     1     1     6 N668DN  DL      Delta Air Lines Inc.    
##  6  2013     1     1     5 N39463  UA      United Air Lines Inc.   
##  7  2013     1     1     6 N516JB  B6      JetBlue Airways         
##  8  2013     1     1     6 N829AS  EV      ExpressJet Airlines Inc.
##  9  2013     1     1     6 N593JB  B6      JetBlue Airways         
## 10  2013     1     1     6 N3ALAA  AA      American Airlines Inc.  
## # … with 336,766 more rows

Tetapi ini sulit untuk digeneralisasi ketika kita perlu mencocokkan beberapa variabel, dan membutuhkan pembacaan yang cermat untuk mengetahui maksud keseluruhan.

Bagian berikut menjelaskan, secara rinci, cara kerja mutating joins. Kita akan mulai dengan mempelajari representasi visual yang berguna dari gabungan. Kita kemudian akan menggunakannya untuk menjelaskan empat mutating join functions : inner join, dan tiga outer joins. Saat bekerja dengan data nyata, kunci tidak selalu mengidentifikasi pengamatan secara unik, jadi selanjutnya kita akan membicarakan tentang apa yang terjadi jika tidak ada kecocokan unik. Terakhir, Anda akan mempelajari cara memberi tahu fungsi dplyr tentang variabel mana yang merupakan kunci untuk gabungan tertentu.

Mendefinisikan kolom kunci

Selama ini, pasangan tabel selalu digabungkan dengan satu variabel, dan variabel tersebut memiliki nama yang sama di kedua tabel. Batasan itu dikodekan oleh = “key”. Kita dapat menggunakan nilai lain untuk oleh untuk menyambungkan tabel dengan cara lain:

1. Defaultnya, by = NULL, menggunakan semua variabel yang muncul di kedua tabel, yang disebut natural join. Misalnya, tabel flights dan weather cocok dengan variabel umumnya: year, month, day, hour dan origin.

Lagkah 22

flights2 %>% 
  left_join(weather)
## Joining, by = c("year", "month", "day", "hour", "origin")
## # A tibble: 336,776 x 18
##     year month   day  hour origin dest  tailnum carrier  temp  dewp humid
##    <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>   <dbl> <dbl> <dbl>
##  1  2013     1     1     5 EWR    IAH   N14228  UA       39.0  28.0  64.4
##  2  2013     1     1     5 LGA    IAH   N24211  UA       39.9  25.0  54.8
##  3  2013     1     1     5 JFK    MIA   N619AA  AA       39.0  27.0  61.6
##  4  2013     1     1     5 JFK    BQN   N804JB  B6       39.0  27.0  61.6
##  5  2013     1     1     6 LGA    ATL   N668DN  DL       39.9  25.0  54.8
##  6  2013     1     1     5 EWR    ORD   N39463  UA       39.0  28.0  64.4
##  7  2013     1     1     6 EWR    FLL   N516JB  B6       37.9  28.0  67.2
##  8  2013     1     1     6 LGA    IAD   N829AS  EV       39.9  25.0  54.8
##  9  2013     1     1     6 JFK    MCO   N593JB  B6       37.9  27.0  64.3
## 10  2013     1     1     6 LGA    ORD   N3ALAA  AA       39.9  25.0  54.8
## # … with 336,766 more rows, and 7 more variables: wind_dir <dbl>,
## #   wind_speed <dbl>, wind_gust <dbl>, precip <dbl>, pressure <dbl>,
## #   visib <dbl>, time_hour <dttm>

2. Vektor karakter, dengan = “x”. Ini seperti gabungan alami, tetapi hanya menggunakan beberapa variabel umum. Misalnya, flights dan planes memiliki variabel year, tetapi artinya berbeda, jadi kami hanya ingin menggabungkan berdasarkan tailnum.

Lagkah 23

flights2 %>% 
  left_join(planes, by = "tailnum")
## # A tibble: 336,776 x 16
##    year.x month   day  hour origin dest  tailnum carrier year.y type 
##     <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>    <int> <chr>
##  1   2013     1     1     5 EWR    IAH   N14228  UA        1999 Fixe…
##  2   2013     1     1     5 LGA    IAH   N24211  UA        1998 Fixe…
##  3   2013     1     1     5 JFK    MIA   N619AA  AA        1990 Fixe…
##  4   2013     1     1     5 JFK    BQN   N804JB  B6        2012 Fixe…
##  5   2013     1     1     6 LGA    ATL   N668DN  DL        1991 Fixe…
##  6   2013     1     1     5 EWR    ORD   N39463  UA        2012 Fixe…
##  7   2013     1     1     6 EWR    FLL   N516JB  B6        2000 Fixe…
##  8   2013     1     1     6 LGA    IAD   N829AS  EV        1998 Fixe…
##  9   2013     1     1     6 JFK    MCO   N593JB  B6        2004 Fixe…
## 10   2013     1     1     6 LGA    ORD   N3ALAA  AA          NA <NA> 
## # … with 336,766 more rows, and 6 more variables: manufacturer <chr>,
## #   model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>

Perhatikan bahwa variabel year (yang muncul di kedua bingkai data masukan, tetapi tidak dibatasi agar sama) tidak ambigu dalam keluaran dengan sufiks.

3. Vektor karakter bernama: oleh = c (“a” = “b”). Ini akan mencocokkan variabel a di tabel x dengan variabel b di tabel y. Variabel dari x akan digunakan dalam output.

Misalnya jika kita ingin menggambar peta kita perlu menggabungkan data flights dengan data airports yang berisi lokasi (lat dan lon) dari setiap bandara. Setiap flight memiliki airport asal dan tujuan, jadi kita perlu menentukan mana yang ingin kami ikuti:

Lagkah 24

flights2 %>% 
  left_join(airports, c("dest" = "faa"))
## # A tibble: 336,776 x 15
##     year month   day  hour origin dest  tailnum carrier name    lat   lon   alt
##    <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>   <chr> <dbl> <dbl> <dbl>
##  1  2013     1     1     5 EWR    IAH   N14228  UA      Geor…  30.0 -95.3    97
##  2  2013     1     1     5 LGA    IAH   N24211  UA      Geor…  30.0 -95.3    97
##  3  2013     1     1     5 JFK    MIA   N619AA  AA      Miam…  25.8 -80.3     8
##  4  2013     1     1     5 JFK    BQN   N804JB  B6      <NA>   NA    NA      NA
##  5  2013     1     1     6 LGA    ATL   N668DN  DL      Hart…  33.6 -84.4  1026
##  6  2013     1     1     5 EWR    ORD   N39463  UA      Chic…  42.0 -87.9   668
##  7  2013     1     1     6 EWR    FLL   N516JB  B6      Fort…  26.1 -80.2     9
##  8  2013     1     1     6 LGA    IAD   N829AS  EV      Wash…  38.9 -77.5   313
##  9  2013     1     1     6 JFK    MCO   N593JB  B6      Orla…  28.4 -81.3    96
## 10  2013     1     1     6 LGA    ORD   N3ALAA  AA      Chic…  42.0 -87.9   668
## # … with 336,766 more rows, and 3 more variables: tz <dbl>, dst <chr>,
## #   tzone <chr>
flights2 %>% 
  left_join(airports, c("origin" = "faa"))
## # A tibble: 336,776 x 15
##     year month   day  hour origin dest  tailnum carrier name    lat   lon   alt
##    <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>   <chr> <dbl> <dbl> <dbl>
##  1  2013     1     1     5 EWR    IAH   N14228  UA      Newa…  40.7 -74.2    18
##  2  2013     1     1     5 LGA    IAH   N24211  UA      La G…  40.8 -73.9    22
##  3  2013     1     1     5 JFK    MIA   N619AA  AA      John…  40.6 -73.8    13
##  4  2013     1     1     5 JFK    BQN   N804JB  B6      John…  40.6 -73.8    13
##  5  2013     1     1     6 LGA    ATL   N668DN  DL      La G…  40.8 -73.9    22
##  6  2013     1     1     5 EWR    ORD   N39463  UA      Newa…  40.7 -74.2    18
##  7  2013     1     1     6 EWR    FLL   N516JB  B6      Newa…  40.7 -74.2    18
##  8  2013     1     1     6 LGA    IAD   N829AS  EV      La G…  40.8 -73.9    22
##  9  2013     1     1     6 JFK    MCO   N593JB  B6      John…  40.6 -73.8    13
## 10  2013     1     1     6 LGA    ORD   N3ALAA  AA      La G…  40.8 -73.9    22
## # … with 336,766 more rows, and 3 more variables: tz <dbl>, dst <chr>,
## #   tzone <chr>

Filtering joins

Memfilter gabungan pengamatan cocok dengan cara yang sama seperti mutating joins, tapi mempengaruhi pengamatan, bukan variabel. Ada dua jenis:

1. semi_join (x, y) menyimpan semua observasi di x yang memiliki kecocokan di y.

2. anti_join (x, y) menghapus semua observasi di x yang memiliki kecocokan di y.

Semi_join berguna untuk mencocokkan tabel ringkasan yang difilter kembali ke baris asli. Misalnya, bayangkan kita telah menemukan sepuluh tujuan terpopuler:

Lagkah 25

top_dest <- flights %>%
  count(dest, sort = TRUE) %>%
  head(10)
top_dest
## # A tibble: 10 x 2
##    dest      n
##    <chr> <int>
##  1 ORD   17283
##  2 ATL   17215
##  3 LAX   16174
##  4 BOS   15508
##  5 MCO   14082
##  6 CLT   14064
##  7 SFO   13331
##  8 FLL   12055
##  9 MIA   11728
## 10 DCA    9705

Sekarang kita ingin menemukan setiap penerbangan yang menuju ke salah satu tujuan tersebut. Kita dapat membuat filter sendiri:

Lagkah 26

flights %>% 
  filter(dest %in% top_dest$dest)
## # A tibble: 141,145 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
##  1  2013     1     1      542            540         2      923            850
##  2  2013     1     1      554            600        -6      812            837
##  3  2013     1     1      554            558        -4      740            728
##  4  2013     1     1      555            600        -5      913            854
##  5  2013     1     1      557            600        -3      838            846
##  6  2013     1     1      558            600        -2      753            745
##  7  2013     1     1      558            600        -2      924            917
##  8  2013     1     1      558            600        -2      923            937
##  9  2013     1     1      559            559         0      702            706
## 10  2013     1     1      600            600         0      851            858
## # … with 141,135 more rows, and 11 more variables: arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>

Tetapi sulit untuk memperluas pendekatan itu ke banyak variabel. Misalnya, bayangkan kita menemukan 10 hari dengan rata-rata penundaan tertinggi. Bagaimana Anda membuat pernyataan filter yang menggunakan tahun, bulan, dan hari untuk mencocokkannya kembali ke penerbangan?

Sebagai gantinya kita dapat menggunakan semi-join, yang menghubungkan dua tabel seperti mutating join, tetapi alih-alih menambahkan kolom baru, hanya pertahankan baris dalam x yang memiliki kecocokan di y:

Lagkah 27

flights %>% 
  semi_join(top_dest)
## Joining, by = "dest"
## # A tibble: 141,145 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
##  1  2013     1     1      542            540         2      923            850
##  2  2013     1     1      554            600        -6      812            837
##  3  2013     1     1      554            558        -4      740            728
##  4  2013     1     1      555            600        -5      913            854
##  5  2013     1     1      557            600        -3      838            846
##  6  2013     1     1      558            600        -2      753            745
##  7  2013     1     1      558            600        -2      924            917
##  8  2013     1     1      558            600        -2      923            937
##  9  2013     1     1      559            559         0      702            706
## 10  2013     1     1      600            600         0      851            858
## # … with 141,135 more rows, and 11 more variables: arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>

Anti-gabungan berguna untuk mendiagnosis ketidakcocokan gabungan. Misalnya, saat menghubungkan penerbangan dan pesawat, Anda mungkin tertarik untuk mengetahui bahwa ada banyak penerbangan yang tidak memiliki kecocokan dalam pesawat:

Lagkah 28

flights %>%
  anti_join(planes, by = "tailnum") %>%
  count(tailnum, sort = TRUE)
## # A tibble: 722 x 2
##    tailnum     n
##    <chr>   <int>
##  1 <NA>     2512
##  2 N725MQ    575
##  3 N722MQ    513
##  4 N723MQ    507
##  5 N713MQ    483
##  6 N735MQ    396
##  7 N0EGMQ    371
##  8 N534MQ    364
##  9 N542MQ    363
## 10 N531MQ    349
## # … with 712 more rows

Tugas untuk mendapatkan sertifikat kelulusan, hasil di upload di Facebook

Buatkan informasi tentang kondisi bandara dan di upload dan publish secara online di RShiny, salah satunya tentang penundaan rata-rata berdasarkan tujuan, lalu gabungkan pada bingkai data bandara sehingga kita dapat menunjukkan distribusi spasial dalah satunya tentang penundaan. Berikut contoh cara mudah menggambar peta Amerika Serikat dan bandara nya:

airports %>%
  semi_join(flights, c("faa" = "dest")) %>%
  ggplot(aes(lon, lat)) +
    borders("state") +
    geom_point() +
    coord_quickmap()