Fidbek se upućuje na goran.milovanovic@datakolektiv.com
. Ova sveščica prati kurs Uvod u R programiranje za analizu podataka 2020/21.
U Sesiji 06 bavimo se relacionim strukturama podataka u programskom jeziku R kroz {tidyverse} paket {dplyr}. Relacione strukture podataka podrazumevaju rad kojim artikulišemo više tabela - ako govorimo o relacionim bazama podataka kojima uobičajeno pristupamo nekom verzijom SQL jezika - odn. više objekata data.frame
klase ako govorimo o programskom jeziku R. Važno: relacione strukture su veoma važne i bez njih je nezamisliv svakodnevni rad u Data Science. Pratićemo prilično ono sadržaj poglavlja 13. Relational Data iz knjige R for Data Science koju koristimo na ovom kursu. Ako imate prethodnog iskustva u radu sa SQL, ovo će za vas biti verovatno lako; ako nemate, savetuje se da pročitate poglavlje 13. pažljivo pre početka ove sesije našeg kursa.
left_join()
i right_join()
funkcijelibrary(tidyverse)
Pogledajmo sledeće dve tabele:
employees <- data.frame(id = 1:5,
name = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
family_name = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
salary = c(1000, 700, 700, 1100, 1000),
dept = c("English", "Italian", "English", "Serbian", "Russian"),
stringsAsFactors = FALSE)
print(employees)
## id name family_name salary dept
## 1 1 Ana Petrovic 1000 English
## 2 2 Marija Markovic 700 Italian
## 3 3 Petar Stosic 700 English
## 4 4 Marko Manojlovic 1100 Serbian
## 5 5 Jelena Petrovic 1000 Russian
departments <- data.frame(id = 1:4,
dept = c("English", "Serbian", "Italian", "Russian"),
size = c(20, 10, 7, 13),
stringsAsFactors = FALSE)
print(departments)
## id dept size
## 1 1 English 20
## 2 2 Serbian 10
## 3 3 Italian 7
## 4 4 Russian 13
Pretpostavimo da treba da uporedimo visinu primanja koju nalazimo u employees$salary
sa veličinom svakog departmana u kompaniji. Mi te podatke u ovom minimalističkom primeru nemamo na jednom mestu: visine primanja zaposlenih nalazimo u employees
dok veličine departmana nalazimo u departments$size
. Šta radimo?
Ako pogledamo obe tabele, videćemo da se kolona dept
nalazi u obe. Obe tabele takođe sadrže kolonu id
, ali tu moramo da budemo veoma oprezni: dok u tabeli employees
to polje identifikuje svakog zaposlenog, istoimeno polje u departments
idenitifikuje odeljenja u kompaniji - a ne zaposlene.
Srećom, vidimo da se polje dept
u obe tabele poklapa po vrednostima i idenitifikuje odeljenje i u jednoj, i u drugoj tabeli. Ono će igrati ulogu ključa u jednostavnoj left_join() operaciji u kojoj ćemo podatke iz tabele employees
dodati podacima u tabeli departments
. U ovoj operaciji, tabela departments
igra ulogu leve, a tabela employees
ulogu desne tabele. Pogledajmo:
departments <- left_join(departments,
select(employees,
salary,
dept),
by = "dept")
print(departments)
## id dept size salary
## 1 1 English 20 1000
## 2 1 English 20 700
## 3 2 Serbian 10 1100
## 4 3 Italian 7 700
## 5 4 Russian 13 1000
Pažljivo pogledajmo šta se dogodilo:
employees
preselili i u levu tabelu depts
;depts
u početku imala četiri reda, dok ih sada ima pet;emplyees
našle dve opservacije - Ana
i Petar
, preciznije - koje su na dept
imale vrednost English
koju smo prethodno nalazili samo jednom u levoj tabeli departments
;left_join()
unosi nove redove u levu tabelu ukoliko postoji više opservacija u desnoj tabeli koje zadovoljavaju ključ u levoj!id.x
i id.y
u departments
: {dplyr} left_join()
je našla kolonu po imenu id
u obe tabele, pa je promenila u levoj tabeli id
u id.x
a iz desne tabele id
prenela u id.y
kako bismo mogli da ih razlikujemo (napomena: iako je ovakvo ponašanje funkcije po difoltu korisno, ovakva promena imena kolona je nešto što u principu želite da izbegnete jer unosi konfuziju u imena kolona tabele sa kojom nastavljate obradu podataka).Analiza ovde nije mnogo korisna pošto imamo samo po jednu opservaciju na vrednostima Italian
, Russian,
i Serbian
u ovom minimalističkom primeru, ali evo:
departments %>%
select(dept, salary) %>%
group_by(dept) %>%
summarise(mean_salary = mean(salary),
median_salary = median(salary),
n_obs = n())
Naravno, da biste izbegli dovlačenje id
kolone iz desne u levu tabelu - što je proizvelo pojavu kolona id.x
i id.y
u rezultatu primene left_join()
:
# - ponovo ćemo napraviti tabelu departments:
departments <- data.frame(id = 1:4,
dept = c("English", "Serbian", "Italian", "Russian"),
size = c(20, 10, 7, 13),
stringsAsFactors = FALSE)
# - ide left_join() sa pipe operatorom:
departments <- departments %>%
left_join(select(employees,
-id),
by = "dept")
print(departments)
## id dept size name family_name salary
## 1 1 English 20 Ana Petrovic 1000
## 2 1 English 20 Petar Stosic 700
## 3 2 Serbian 10 Marko Manojlovic 1100
## 4 3 Italian 7 Marija Markovic 700
## 5 4 Russian 13 Jelena Petrovic 1000
Funkcija left_join()
ima sestru koja se (zamislite) zove right_join()
. Ponovo ćemo napraviti naše tabele kakve su bile pre left_join()
:
employees <- data.frame(id = 1:5,
name = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
family_name = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
salary = c(1000, 700, 700, 1100, 1000),
dept = c("English", "Italian", "English", "Serbian", "Russian"),
stringsAsFactors = FALSE)
print(employees)
## id name family_name salary dept
## 1 1 Ana Petrovic 1000 English
## 2 2 Marija Markovic 700 Italian
## 3 3 Petar Stosic 700 English
## 4 4 Marko Manojlovic 1100 Serbian
## 5 5 Jelena Petrovic 1000 Russian
departments <- data.frame(id = 1:4,
dept = c("English", "Serbian", "Italian", "Russian"),
size = c(20, 10, 7, 13),
stringsAsFactors = FALSE)
print(departments)
## id dept size
## 1 1 English 20
## 2 2 Serbian 10
## 3 3 Italian 7
## 4 4 Russian 13
Isti rezultat kao prethodni možemo da dobijemo primenom right_join()
i zamenom mesta argumenata:
# - ide right_join() sa pipe operatorom:
departments <- select(employees, -id) %>%
right_join(departments,
by = "dept")
print(departments)
## name family_name salary dept id size
## 1 Ana Petrovic 1000 English 1 20
## 2 Marija Markovic 700 Italian 3 7
## 3 Petar Stosic 700 English 1 20
## 4 Marko Manojlovic 1100 Serbian 2 10
## 5 Jelena Petrovic 1000 Russian 4 13
Obratite pažnju: u rezultatu se promenila samo jedna stvar. Koja?
Ponekad je potrebno spojiti dve tabele preko dva ključa: na primer, po imenu, i prezimenu korisnika nekog sistema ili zaposlenog. Ponekad - a to se najčešće dešava kada ste “prljavo programirali”, ne planirajući dobro vaše strukture podataka - treba uraditi join operaciju na suštinski istom ključu koji se u dve tabele javlja pod dva različita imena.
Razmotrimo prvo join po dva ključa. Već znamo employees
tabelu:
employees <- data.frame(id = 1:5,
name = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
family_name = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
salary = c(1000, 700, 700, 1100, 1000),
dept = c("English", "Italian", "English", "Serbian", "Russian"),
stringsAsFactors = FALSE)
print(employees)
## id name family_name salary dept
## 1 1 Ana Petrovic 1000 English
## 2 2 Marija Markovic 700 Italian
## 3 3 Petar Stosic 700 English
## 4 4 Marko Manojlovic 1100 Serbian
## 5 5 Jelena Petrovic 1000 Russian
Sad da dodamo još jednu tabelu:
employees_bd <- data.frame(name = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
family_name = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
birthday = c("1991-10-08",
"1985-07-10",
"1991-04-03",
"1991-12-01",
"1991-12-20"),
stringsAsFactors = FALSE)
print(employees_bd)
## name family_name birthday
## 1 Ana Petrovic 1991-10-08
## 2 Marija Markovic 1985-07-10
## 3 Petar Stosic 1991-04-03
## 4 Marko Manojlovic 1991-12-01
## 5 Jelena Petrovic 1991-12-20
Ok, druga tabela employees_bd
ne samo što nema id
kolonu sa vrednostima koje bi pomogle da ona igra ulogu ključa za spajanje sa employees
tabelom - već ona nema id
kolonu uopšte! Ako želimo da podatke iz employees
dodamo podacima u employees_bd
ne preostaje nam ništa drugo do da ih spajamo po dve kolone koje obe tabele sadrže: name
i family_name
:
employees_bd <- employees_bd %>%
left_join(employees,
by = c("name", "family_name"))
print(employees_bd)
## name family_name birthday id salary dept
## 1 Ana Petrovic 1991-10-08 1 1000 English
## 2 Marija Markovic 1985-07-10 2 700 Italian
## 3 Petar Stosic 1991-04-03 3 700 English
## 4 Marko Manojlovic 1991-12-01 4 1100 Serbian
## 5 Jelena Petrovic 1991-12-20 5 1000 Russian
A šta ako smo baš zabrljali pa employees_bd
tabela izgleda ovako:
employees_bd <- data.frame(ime = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
prezime = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
birthday = c("1991-10-08",
"1985-07-10",
"1991-04-03",
"1991-12-01",
"1991-12-20"),
stringsAsFactors = FALSE)
print(employees_bd)
## ime prezime birthday
## 1 Ana Petrovic 1991-10-08
## 2 Marija Markovic 1985-07-10
## 3 Petar Stosic 1991-04-03
## 4 Marko Manojlovic 1991-12-01
## 5 Jelena Petrovic 1991-12-20
Onda se to u {dplyr} radi ovako:
employees_bd <- employees_bd %>%
left_join(employees,
by = c("ime" = "name",
"prezime" = "family_name"))
print(employees_bd)
## ime prezime birthday id salary dept
## 1 Ana Petrovic 1991-10-08 1 1000 English
## 2 Marija Markovic 1985-07-10 2 700 Italian
## 3 Petar Stosic 1991-04-03 3 700 English
## 4 Marko Manojlovic 1991-12-01 4 1100 Serbian
## 5 Jelena Petrovic 1991-12-20 5 1000 Russian
Razmislite: kako je left_join()
odabrala da imenuje kolone rezultata u ovom primeru?
A da je Perica odmah odabrao da ove dve tabele imaju iste id
kolone, što bi svako ko pažljivo planira relacione strukture za analizu uradio…
employees_bd <- data.frame(id = 1:5,
ime = c("Ana", "Marija", "Petar", "Marko", "Jelena"),
prezime = c("Petrovic", "Markovic", "Stosic", "Manojlovic", "Petrovic"),
birthday = c("1991-10-08",
"1985-07-10",
"1991-04-03",
"1991-12-01",
"1991-12-20"),
stringsAsFactors = FALSE)
employees_bd <- employees_bd %>%
left_join(employees,
by = "id")
print(employees_bd)
## id ime prezime birthday name family_name salary dept
## 1 1 Ana Petrovic 1991-10-08 Ana Petrovic 1000 English
## 2 2 Marija Markovic 1985-07-10 Marija Markovic 700 Italian
## 3 3 Petar Stosic 1991-04-03 Petar Stosic 700 English
## 4 4 Marko Manojlovic 1991-12-01 Marko Manojlovic 1100 Serbian
## 5 5 Jelena Petrovic 1991-12-20 Jelena Petrovic 1000 Russian
Ne, Perice, kad planiraš relacione strukture ne želiš da udvajaš podatke, ne trebaju ti ime
i name
, prezime
i family
name; relacione strukture naime služe upravo tome da se uštedi u reprezentaciji podataka, da se ona optimizuje, a ne tako… Ovako:
employees_bd <- data.frame(id = 1:5,
birthday = c("1991-10-08",
"1985-07-10",
"1991-04-03",
"1991-12-01",
"1991-12-20"),
stringsAsFactors = FALSE)
print(employees_bd)
## id birthday
## 1 1 1991-10-08
## 2 2 1985-07-10
## 3 3 1991-04-03
## 4 4 1991-12-01
## 5 5 1991-12-20
I onda samo:
employees_bd <- employees_bd %>%
left_join(employees,
by = "id")
print(employees_bd)
## id birthday name family_name salary dept
## 1 1 1991-10-08 Ana Petrovic 1000 English
## 2 2 1985-07-10 Marija Markovic 700 Italian
## 3 3 1991-04-03 Petar Stosic 700 English
## 4 4 1991-12-01 Marko Manojlovic 1100 Serbian
## 5 5 1991-12-20 Jelena Petrovic 1000 Russian
R Markdown je ono što koristimo da bismo razvili ove sveščice. Evo knjige iz koje se može naučiti rad u toj jednostavnoj ekstenziji R: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds..
Goran S. Milovanović, Data Scientist & Vlasnik, DataKolektiv.
Kontakt: goran.milovanovic@datakolektiv.com. Ovo je besplatan i slobodan softver: GPL v2.0.