พื้นฐาน R สามารถอ่าน Blog ตอนก่อนหน้าได้น้า
สำหรับวันนี้แอดทอยมา Recap ที่สอน Data Transformation มีตามนี้น้า
Glue Package
Glue เป็น package ที่ทำหน้าต่อต่อคำ แบบเดียวกับ paste หรือ paste0
- install package + load
install.packages("glue") library(glue) # include lib ก่อนใช้งาน
- sample use - ผมชอบแบบนี้นะ เหมือน C# พอดีเลย
# string template my_name <- "Pingkunga" my_age <- 34 # String template in base R is paste 0 text <- glue("Hi! my name is {my_name} age about {my_age}") print(text) #
ผมชอบแบบนี้นะ เหมือน C# String interpolation แบบ $"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now."
Recap Data Frame Func
mtcars
- build in DataSet ใน RView(YOUR_DataFrame_Name)
- แสดงข้อมูลในรูปแบบตาราง หลังจากนี้ใช้ mtcarshead(mtcars, 5)
- ดึงข้อมูลมา 5 แถวแรกtail(mtcars, 5)
- ดึงข้อมูลมา 5 แถวท้ายglimpse(mtcars)
และstr(mtcars)
ดูโครงสร้างของ Data Frame
-str
มาจาก base r
-glimpse
มาจาก dplyr มาแก้ปัญหาการแสดงผลของ strsummary(mtcars)
แสดง basic stat Min / 1st Quartile / Median / Mean / 3rd Quartile / Max ของแต่ละ Columnnames(mtcars)
/colnames(mtcars)
- List Column in dfrownames(mtcars)
- เพิ่งรู้ว่ามีแบบนี้ด้วย ชื่อแถว อารมณ์แบบ pk ใน table
best practice: move rownames to col name จะได้ manage สะดวก
# best practice move rownames to col name mtcars$model mtcars$model <- rownames(mtcars) # remove rowname rownames(mtcars) <- NULL View(mtcars)
dplyr - data transformation
- Data Pipeline
เราสามารถให้ output ของแต่ละคำสั่ง มันส่งต่อกันไปได้เรื่อยๆ เหมือนสายก่ารผลิด โดยจะเรียกว่า Pipeline
- เดิม - ดึง column 1-5 จาก df mtcars
select(mtcars,1:5)
- Pipeline มี 2 แบบ
%>%
or|>
(new Style)
mtcars %>% select(1:5) #นอกจากนี้ทำ Chain ต่อได้ ดึง column 1-5 จาก df mtcars จากนั้นกรอง 5 แถวแรกขึ้นมา # - แบบ %>% mtcars %>% select(1:5) %>% head() # - แบบ |> mtcars |> select(1:5) |> head()
- select
ดึงข้อมูลออกมาจาก Data Frame โดยมีหลายแบบ อารมณ์เหมือน SQL SELECT
- ระบุ Id ของ Column
# เอา Column ที่ 1 - 5 mtcars |> select(1:5) # เอา Column ที่ 2 mtcars |> select(2) # เอา Column ที่ 2 / 10 mtcars |> select(2,10)
- ระบุ ชื่อ Column
mtcars |> select(model) mtcars |> select(am, hp, wt, model)
- ผสม
# move model to left size select(mtcars, model, 1:5) # Pipeline Style mtcars |> select(model, 1:5)
- เอา helper verb มาช่วย
# 1. everything() เอา Column ที่เหลือที่ไม้ได้ระบุตอน Select มาต่อได้ เคสนี้ ระบุ model และให้เอาที่เหลือมาต่อท้าย mtcars |> select(model, everything()) # 2. starts_with หา Column ที่ชื่อต้นทางที่ระบุ mtcars |> select(starts_with("h")) # 3. ends_with หา Column ที่ลงท้าย mtcars |> select(ends_with("p")) # 4. contains หา Column ที่มีตัวอักษร mtcars |> select(contains("a"))
- filter
- Concept เหมือน SQL / Google Sheet ตัวอย่างการใช้งานตามนี้เลย
# Simple Condition m |> filter(mpg > 30) m |> filter(mpg < 15) # and & m |> select(1:5) |> filter(hp < 100 & disp < 100) # or | m |> select(1:5) |> filter(hp < 100 | disp < 100) # หลายเงื่อนไข เคส AND & m |> filter(mpg < 15 & hp > 240) # หลายเงื่อนไข เคส AND & + SELECT m |> select(model, mpg, hp, wt) |> filter(mpg < 15 & hp > 240 & wt > 3.8)
- With RegEx เอา Regular Expression มาช่วย
-grep(YOUR_RegEx, TEST_VECTOR)
- return vector ของ index ที่พบ
-grepl(YOUR_RegEx, TEST_VECTOR)
- return boolean vector TRUE พบ กลับกัน FALSE
# recap RegEx state.name # build in dataset # ====================================== # grep sample # ดูว่ามี state ไหนที่ขึ้นต้นด้วย A grep("^A", state.name) #[1] 1 2 3 4 # ลองดึงชื่อ state จริงๆ state.name[1:4] # ดูว่ามี state ไหนทีลงท้ายด้วย A grep("a$", state.name) #ลองดึงชื่อออกมา state.name[grep("a$", state.name)] # ====================================== # grepl sample # return true / false grepl("^A", state.name) state.name[grepl("^A", state.name)]
- เอา Grep ไปใช้กับ Filter
# Start with Merc* m |> filter(grepl("^Merc", model)) %>% select(model, am, wt, hp) # start with H or T or P m |> filter(grepl("^[HTP]", model)) %>% select(model, am, wt, hp) # not start with H or T or P m |> filter(grepl("^[^HTP]", model)) %>% select(model, am, wt, hp)
- mutate (Create New Column
- สร้าง Column ใหม่ จาก hp มาเป็น hp_double
# จาก hp มาเป็น hp_double m|> select(model, hp) |> mutate(hp_double = hp * 2) |> head() จาก hp มาเป็น hp_double / hp_addfive ต่อๆกัน m|> select(model, hp) |> mutate(hp_double = hp * 2, hp_half = hp /2, hp_addfive = hp + 5) |> head()
- นอกจากนี้ เอา Column ที่สร้างมาใช่ต่อได้ ตัวอย่างเอา hp_half มาใช้ต่อ hp_addfive / hp_per_wt
m|> select(model, hp, wt) |> mutate(hp_double = hp * 2, hp_half = hp /2, hp_addfive = hp_half + 5, hp_per_wt = hp_half/wt) |> head()
- ถ้าต้องการเติมข้อมูลทุก Columnให้เหมือน (R - Recycle or Python broadcast) ใช้
Random()
m|> select(model, hp) |> mutate(hp_double = hp * 2, hp_half = hp /2, hp_addfive = hp + 5, # recycle or broadcast in python fill row random = "fill_your_word") |> head()
- ใช้ if_else / case_when มาจัดกลุ่มข้อมูล ทำ Segment
# if-else create segment column # ifelse - classic base r # if_else - modern r perf improve m |> select(model, hp, wt) |> mutate(segment = if_else(hp > 200, "High", "Low")) |> mutate(segment = case_when( hp > 200 ~ "high", hp > 100 ~ "medium", ## else TRUE ~ "low" )) m |> select(model, hp, wt) |> mutate(segment = if_else(hp > 200, "High", "Low")) |> mutate(segment = case_when( hp > 200 ~ "high", hp > 100 ~ "medium", hp <= 100 ~ "low" ))
- arrange - sort data เหมือน SQL Order By
ปกติจะเรียงจากน้อยไปมาก ถ้าต้องการจากมากไปน้อยใช้ desc()
- Single Column
# single Column m |> select(model, hp, wt) |> arrange(model) m |> select(model, hp, wt) |> arrange(desc(model))
- multi-column sort
# multi-column sort m |> select(model, hp, wt, am) |> arrange(am ,hp) m |> select(model, hp, wt, am) |> arrange(am , desc(hp))
- summarise สรุปข้อมูลทางสถิติ
m |> select(model, am, hp) |> mutate(am = if_else(am==0, "Auto", "Manual")) |> summarise(avp_hp = mean(hp), sum_hp = sum(hp), med_hp = median(hp), sd_hp = sd(hp), var_hp = var(hp), n = n()) # result avp_hp sum_hp med_hp sd_hp var_hp n 1 146.6875 4694 123 68.56287 4700.867 32
- ถ้าต้องการแบ่งกลุ่มใช้ Group By
m |> select(model, am, hp) |> mutate(am = if_else(am==0, "Auto", "Manual")) |> group_by(am) |> summarise(avp_hp = mean(hp), sum_hp = sum(hp), med_hp = median(hp), sd_hp = sd(hp), var_hp = var(hp), n = n()) # result # A tibble: 2 × 7 am avp_hp sum_hp med_hp sd_hp var_hp n <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <int> 1 Auto 160. 3045 175 53.9 2906. 19 2 Manual 127. 1649 109 84.1 7066. 13
- Function อื่นๆ (sample / slice / pull / rename)
sample
() สุ่มข้อมูลขึ้นมา จากเดิมมี head() / tail() อันนี้ช่ววยให้เลือกได้ตัวแทนได้กระจายขึ้น
# sample m |> sample_n(5) # sample random m |> sample_frac(0.5) # sample random 50%
- slice() แบ่งข้อมูลจาก Data Frame
# เดิมๆใน df m[c(1,3,5),] # Slice m |> slice(1:5) #เลือกบาง Column ใส่ Vector เข้าไป m |> slice(c(1,3,5))
- pull ดึง Column ออกจาก Data Frame จะได้เป็น Vector
# pull - return as vector a = m |> filter(hp > 200) |> pull(model) class(a) typeof(a) # check is vector https://stackoverflow.com/questions/19501186/how-to-test-if-object-is-a-vector is.atomic(a) # TRUE # use model a Vector Element Name m %>% filter(hp > 100) %>% pull(hp, model)
- rename เปลี่ยนชื่อ Column จริงๆมันคล้ายกับ select
m |> select(model, horse_power = hp, tranmission = am, weight = wt) |> head() # rename column model to car -- same as select m |> rename(car = model, weight = wt)
ใช้ SQL Query ข้อมูลจาก Data Frame
- sqldf = lib ที่ช่วยใช้ SQL Query ข้อมูลจาก Data Frame
- m = dataframe
install.packages("sqldf") library(sqldf) # SQL Case In-sensitive sqldf("SELECT model, am , hp FROM m") sqldf("SELECT am , AVG(hp), SUM(hp) FROM m GROUP BY am")
How to handle missing value = NA
- NA คือ อะไร
- NA ข้อมูลที่ไม่มีค่า โดยปัญหาของมัน ทำให้บางสูตรใช้งานไม่ได้
# missing value nums <- c(1:10, NA, 11:15) # why missing value cause problem mean(nums) # [1] NA sum(nums) # [1] NA
- จริงๆ มันใช้งานได้แหละ แต่ต้องมาตัด NA ออก
na.rm = TRUE
rm = remove
# handle na mean(nums, na.rm = TRUE) sum(nums, na.rm = TRUE)
- ตรวจสอบ Vector Contain และเอามาตัดของที่ไม่มี NA
# Check Vector Contain NA nums[is.na(nums)] # [1] NA # Filter NA out nums[!is.na(nums)] # [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 nums2 <- nums[!is.na(nums)]
- think before filtering NA
อันนี้้แอดทอยยกตัวอย่างการ political preference USA ระหว่าง Democratic / Republican Party จาก 1000 คน มี 300 คนไม่ได้ตอบ มี 300 NA แล้วเรา Handle ยังไง ?
- do nothing > report 300 NA
- find the proxy column to ans (imputation) in NA Column such as like national healthcare (นโยบายของ Democratic) can be performed to democrat
- drop_na remove all rows //should <= 5% (arbitrary) / acceptable rate
- tidyr - package สำหรับ clean data
install.packages("tidyr") # if install tidyverse no run this library(tidyr)
- imputation
มีหลายวิธี ถ้าเอาง่ายๆ แอดทอยให้แนวทาง ดังนี้
- Column ที่ NA เป็น Numeric ใช้ mean / median
# prepare data id <- 1:5 friends <- c("ping", "tarn", "guide", "bank", "kook") spending <- c(100, 150, NA, 200, 180) service <- c("nf", "nf",NA,"amz", "nf") # create df with NA df <- data.frame(id,friends,spending, service) # ========================================= # mean imputation replace NA with mean avg_spending <- mean(df$spending, na.rm=T) avg_spending df2 |> mutate(spending = replace_na(spending, avg_spending)) # ========================================= # median imputation replace NA with median median_spending <- median(df$spending, na.rm=T) df3 |> mutate(spending = replace_na(spending, median_spending))
- Column ที่ NA เป็น String ใช้ majority vote หรือ เอาแบบ Advance k-nearest neighbors
# majority vote counts <- table(df$service) max_value <- names(counts)[which.max(counts)] df4 <- df |> mutate(service = replace_na(service, max_value))
wide vs long data format
จากฟังแอดทอยสอนมา
- wide data format เป็นข้อมูลที่เหมาะสำหรับดูใน report เช่น ข้อมูลจำนวนประชากร แยกตามปี 2000, 2001 ... 2025 สังเกตุว่ามี Column ประเภทเดียวกันที่ซ้ำ ปี
- long data format ผมมองการจัดข้อมูล Normalize Table แบบ 3NF ทุก Column เก็บข้อมูลไม่ซำซ้อนกัน
wide data format
|Country|2000|2001|....|2025| |TH |55M |56M |....|67M | |USA |350M|356M|....|400M |
long data format
|Country|YEAR|POPULATION| |TH |2000| 55M | |TH |2001| 56M | ... |TH |2025| 67M | |USA |2000| 350M |
Sample ใช้ dplyr - pivot_longer / pivot_wider
- pivot_longer - wide > long
- pivot_wider - long > wide
# sample wide data format View(world_bank_pop) # normalize to year + population # dplyr + tidyr convert wide > long data format world_bank_pop |> filter(indicator == "SP.URB.TOTL") |> View() long_df = world_bank_pop |> filter(indicator == "SP.URB.TOTL") |> pivot_longer(cols = `2000`:`2017`, names_to = "year", values_to = "population") # reverse operation pivot_wider wide_df <- long_df |> pivot_wider(names_from = "year", values_from = "population") View(wide_df)
dplyr - JOIN Like SQL
- มีเหมือนกัน แต่เราต้องดูข้อมูลก่อน
> band_members # A tibble: 3 × 2 # name band # <chr> <chr> # 1 Mick Stones # 2 John Beatles # 3 Paul Beatles > band_instruments # A tibble: 3 × 2 # name plays # <chr> <chr> # 1 John guitar # 2 Paul bass # 3 Keith guitar
- SQL - ความสัมพันธ์ของ 2 Data Source ใช้ Column name
SELECT * FROM band_members bm LEFT JOIN band_instruments bi ON bm.name = bi.name
- ลอง Join ใน Column name มีเหมือนกัน
# pipe band_members |> left_join(band_instruments, by = "name") band_members |> inner_join(band_instruments, by = "name")
- กรณีที่ไม่เหมือนกัน สร้าง Vector ของคู่อันดับ Column ที่สัมพันธ์ในที่นี่ pos_id กับ id
# case key name are difference df_emp = data.frame(name= c("ping", "tarn", "kook"), pos_id= c(1, 2, 1)) df_pos = data.frame(id=1:3, name= c("DEV", "QA", "PM")) df_emp |> inner_join(df_pos, by = c("pos_id"="id")) # result # name.x pos_id name.y # 1 ping 1 DEV # 2 tarn 2 QA # 3 kook 1 DEV
R with SQL
- เลือก Lib ก่อน เพราะ DB แต่ละค่ายใช้ Lib ไม่เหมือนกัน เช่น .
- SQLite -RSQLite
- Postgres -RPostgreSQL
- BigQuery -bigrquery
- อ่านคู่มือการใช้งานแต่ละ Lib เช่น RPostgres ดูจากหน้าของ cran.r-project
Sample with SQLite ตัว DB ต้องอยู่ใน Working Directory เดียวกัน
# Set lib install.packages("RSQLite") library(RSQLite) # bigrquery getwd() list.files() # connect Database conn <- dbConnect(SQLite(),"chinook.db") conn # list table dbListTables(conn) # list field dbListFields(conn,"employees") # send SQL to this database dbGetQuery(conn, " SELECT FirstName, City, Country FROM employees WHERE City= 'Lethbridge'") result <- dbGetQuery(conn, " SELECT FirstName, City, Country FROM employees WHERE City= 'Lethbridge'") # write a new table from df library(dplyr) # SQL DROP dbRemoveTable(conn, "models") filter_mtcars <- mtcars |> select(mpg, hp, wt) |> filter(hp > 200) dbWriteTable(conn, "models", filter_mtcars) dbGetQuery(conn, " SELECT * FROM models") # close connection dbDisconnect(conn) # > conn # <SQLiteConnection> # DISCONNECTED # other db # https://cran.r-project.org/web/packages/RPostgres/RPostgres.pdf
Homework - play with nycflights23 data with dyplyr
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts sent to your email.