R/Exploratory data analysis

R for Data Science::transformation

Abokadoh 2023. 6. 6. 20:10

tibble이란

tibble은 data frame이지만, tidyverse에서 더 잘 작동되도록 수정되었다.

library(nycflights13)
library(tidyverse)
head(flights)

int는 정수

dbl은 복소수 또는 실수

chr은 string vector, string

dttm은 날짜-시간(날짜 + 시간)

lgl은 논리, 논리벡터

fctr은 범주형 변수

date는 날짜

를 나타낸다. 

 

dplyr의 대표적인 함수 6개를 알아보자.

  • filter() 값으로 관측값을 선택하는 함수.
  • arrange() 행을 재정렬
  • select() 이름으로 변수를 선택
  • mutate() 새로운 변수 생성
  • summarise() 여러 값을 하나의 요약으로 축소
    • 이러한 함수는 모두 각 함수의 범위를 전체 데이터 집합에서 작동하는 것에서 그룹별로 작동하는 것으로 변경해주는 함수 group_by()와 함께 사용이 가능하다.

위 함수들이 첫 번째로 받는 argument는 dataframe이다. 그 다음은 변수 이름, 함수로 반환되는 결과는 새로운 데이터 프레임일 것이다.

 

1. filter()

filter(flights, month == 1, day == 1)

month와 day의 value가 1인 모든 행을 뽑아낸다.

뽑아낸 것을 jan1에 객체화해보자.

jan1 <- filter(flights, month == 1, day == 1)
필터링을 효과적으로 사용하려면 비교연산자를 사용하여 원하는 관측값을 선택하는 방법을 알아야 한다.
R은 standard suite으로 >, >=, <, <=, !=, ==을 제공한다.

가볍게 코드로 익혀보자

sqrt(2)^2 == 2
1/49 * 49 == 1

위 두 코드는 내가 생각했던 것과 달리 모두 FALSE를 반환한다. 왜 이러한 답이 나오는 지에 대해서는 추후 다루도록 하고.. 이런 코드에 대해서 TRUE를 받고싶을 때 사용하는 코드가 있다.

- near()

near(sqrt(2)^2, 2)
near(1/49 * 49, 1)

near()함수를 사용하면 값이 근사할 때 TRUE를 반환해준다.

 

- & 'and',  | 'or'

filter(flights, month ==11 | month ==12)

11월 또는 12월인 데이터 모두 추출

 

filter(flights, month != 12)
filter(flights, !(month ==12))

12월이 아닌 데이터 모두 추출, 두 코드 모두 같은 결과를 반환한다.

 

- %in%

%in%를 filter에서 사용하면 y의 값 중 하나인 x를 가진 모든 행을 선택한다. 이해가 안되면 실습을 통해 느껴보자.

nov_dec <- filter(flights, month %in% c(11,12))

이렇게 하면 11월 12월을 가지고 있는 데이터를 모두 선택한다고 할 수 있겠다.

 

filter(flights, !(arr_delay > 120 | dep_delay > 120))

위 코드는 여집합의 개념을 담고 있다. arr_delay 가 120보다 크거나 dep_delay 가 120보다 큰 것들이 아닌 것? ㅋㅋ 이라고 해석하면 되니까. 집합으로 생각해서 해석하면 좋을 것 같다.

 

filter(flights, arr_delay <= 120, dep_delay <= 120)
filter(flights, arr_delay <= 120 & dep_delay <= 120)

filter에서 ,과 &은 정확히 같은 기능을 한다.

 

위에서 배운 것을 응용하면 다음의 결과는 명시한 value에 해당하는 모든 row를 선택하여 객체화할 것이다.

nov_dec <- filter(flights, month %in% c(11,12))

month에 11,12를 갖는 모든 행을 뽑아냈다.

- missing values NA 

1. 'NA' value는 not availables을 의미한다.  말그대로 사용할 수 없는 value이다.

2. 'NA'는 전염성이 있다.

NA과의 모든 연산은 NA를 반환한다.

 

코드로 확인해보자. 

NA > 5

NA값이 5보다 큰가에 대한 답은?  NA와의 연산은 항상 NA를 반환한다.

10 == NA

10이 NA와 같나? 에 대한 답은 당연히 NA일 것이다.

NA + 10
NA / 2

위와 같은 코드도 마찬가지.

그렇다면 

NA == NA

오.. NA가 NA이냐?! 라는 답에서도 결국 NA를 받아낸다.

 

- filter()함수의 NA에 대한 반응

필터함수는 TRUE인 조건만을 가져오고 FALSE나 NA값은 배제한다.

 

df <- tibble(x= c(1, NA, 3))
filter(df, x>1)

NA값도 가져오고싶다면 | 연산자를 사용하면 된다.

filter(df, is.na(x) | x > 1)

Exercises 

1. arrival_delay가 2시간 이상인 데이터

2. 목적지가 IAH, HOU인 데이터

3. 항공사명이 UA, DL인 데이터

4. 출발을 여름에 한 데이터 (여름 : 7,8,9월)

5. 2시간이상 도착이 지연됐지만, 늦게 떠나지 않은 데이터

6. 최소 한시간 이상 늦게 출발했지만, 비행으로 30분이상 단축시킨 데이터

7. 자정부터 오전6시 사이에 출발한 데이터

 

4,7 : between() 함수를 사용한 풀이

# ex1
flights %>%  filter(arr_delay >= 120)

# ex2
flights %>%  filter(dest %in% c('IAH', 'HOU'))

# ex3
flights %>% filter(carrier %in% c('UA', 'DL'))

# ex4
flights %>%  filter(month %in% c('7','8','9'))

# ex5
flights %>%  filter(arr_delay >= 120 & dep_delay <= 0)

# ex6
filtered_flights <- flights %>%
  mutate(time_saved = dep_delay - arr_delay) %>% 
  filter(dep_delay >= 60, time_saved >= 30)

# ex7
flights %>% filter(dep_time >= 000 & dep_time <= 600)

### between()함수를 활용한 예제 풀이
#ex4, ex7
flights %>% filter(between(month,7,9))
flights %>% filter(between(dep_time,000,600))

2. arrange()

arrange(flights, year, month, day)

arrange()함수는 특정 변수를 기준으로 데이터의 나열을 바꿔주는 함수이다. 위의 경우는 year, month, day 순으로 데이터를 나열할 것이다. 중요도는 역시 year, month, day순

 

- 내림차순 desc()

arrange(flights, desc(dep_delay))

- arrange의 missing value에 대한 적용.

df <- tibble(x = c(5, 2, NA))
arrange(df, x)
arrange(df, desc(x))

위에서 보이듯 arrange()함수는 기본적으로 NA값을 항상 마지막에 배치한다.

 

Exercises

1. arrange()를 사용했을 때 NA값을 맨 앞으로 나열하는 방법은 무엇인가.

2. flights 데이터를 가장 지연된 비행 순으로 나열하고 가장 일찍 떠난 비행을 찾아라.

3. 가장 빠른 비행기을 찾기 위한 정렬하기.

4. 어떤 비행기가 가장 짧게 비행했고, 길게 비행했는가? 

 

# Ex1
na_ar_fl <- flights %>% arrange(desc(is.na(dep_delay)),dep_delay)

is.na()함수를 이용하는 방법이다. is.na(dep_delay)의 결과는 TRUE, FALSE를 반환하는데 arrange()함수는 TRUE를 먼저  나열하는 성질을 이용하는 방법이다. 기본적으로 Arrange()함수가 NA값을 맨 뒤로 보내기 때문에 사용할 일은 없겠지만, desc(is.na())를 사용하면 TRUE를 앞으로 반환하여 NA를 맨뒤로 보낼 것이다.

 

# Ex2
flights %>% arrange(dep_delay)
flights %>% arrange(desc(dep_delay))

arrange()함수는 특정 변수를 기준으로 오름차순 정열하는 것이 기본값이다. 코드의 결과를 통해 flight number 97번이 가장 일찍 떠난 비행. 51번이 가장 늦게 떠난 비행이라는 것을 확인할 수 있다.

 

 

 

# Ex3
speed_flights <- flights %>% 
  mutate(speed = distance/air_time) %>% 
  arrange(desc(speed))

우선 속도를 구하기 위해 mutate()함수를 사용해 Speed라는 새로운 변수를 만들었다. 그리고 나서 새로 추가한 speed변수를 기준으로 내림차순하면 가장 속력이 빠른 비행기를 찾을 수 있다.  flight number 1499가 가장 빨랐다는 결론이 나왔다.

# Ex4
flights %>% arrange(desc(air_time)) %>% select(air_time,flight)
flights %>% arrange((air_time)) %>% select(air_time,flight)

짧은 비행과 긴 비행을 찾는 문제는 간단하다. 

 

위 코드의 결과로 가장 짧은 비행시간은 20분으로 flight number 4368, 가장 긴 비행시간은 695분 flight number 15이다.

 

3. select()

실제로 수백, 수천개의 변수를 가진 데이터셋을 다루는 경우는 많다. 때문에 우리의 과제는 우리가 특별히 관심있는 변수를 찾고 변수의 개수를 줄이는 것이다. 이 때 사용하는 함수 Select()이다.

 

기존의 flights 데이터는 15개 변수를 가진 데이터이다. 15개의 변수는 한 눈에 보기도 힘들고 다 볼 이유도 없을 수 있다.

때문에 select함수를 사용해 당장 필요하다고 생각한 year, month, day변수만 추출해 보기로 했다.

select(flights, year, month, day)

select(flights, year:day)

flights 데이터는 변수에 순서가 year,month,day 순으로 있기 때문에 이런식으로도 :을 사용할 수도 있다.

 

응용하여 특정 변수를 제외한 모든 변수를 뽑아내는 것도 가능하다.

select(flights, -(year:day))
select(flights, -year, -month, -day)

- rename

select함수는 변수명을 renaming하는데 사용될 수 있다. 하지만 잘 사용되지 않는 이유는 명시하지 않은 모든 변수를 drop시키기 때문이다.

select(flights, deptime = dep_time)

때문에 

colnames(flights) <- colnames(rename(flights, deptime = dep_time))
colnames(flights)

와 같은 방법으로 column명을 변경할 수 있다.

- sorting variables(변수 정렬)

select()함수에 또 다른 옵션인 everything()이 있다. everything()옵션은 특정 변수의  위치를 dataframe의 시작열로 이동시킬 때 유용하다.

select(flights, time_hour, air_time, everything())

- select()함수의 helper functions 

  • starts_with('abc') : abc로 시작하는 names 
  • end_with('xyz') : xyz로 끝나는 names
  • contain('ijk') : ijk가 포함되는 names
  • matches('(.)\|') : 정규식관련된 names  / 정규표현식(regular expressions)에 대해서는 추후 자세히 다루도록 하자.
  • num_range('x', 1:3) : x1,x2,x3

 

start_with() 함수는 select()함수가 특정 문자열로 시작하는 변수를 뽑도록 돕는다.

select(flights, starts_with('dep'))

 

ends_with('xyz')는 특정문자열로 끝나는 변수를 찾도록 한다.

select(flights, ends_with('time'))

contains('ijk') 는 특정 문자열을 포함하는 변수를 찾도록 한다.

select(flights, contains('time'))

num_range('x',1:3) : x1,x2,x3를 찾는다.

 

df <- data.frame(id = 1:3, y1 = 1:3, y2 = 2:4, y3= 3:5, y4=4:6)
select(df, num_range('y',1:3))

 

4. mutate()

존재하는 열을 선택하는 것 외에, 종종 새로운 열을 추가하는 것이 필요할 때가 있다. mutate()함수는 이런 경우 사용된다.

 

flights data에서 year에서 day까지의 변수와 변수명이 delay로 끝나는 변수. 그리고 distance, air_time 변수를 뽑아보자.

flights)sml <- select(flights,
  year:day,
  ends_with('delay'),
  distance,
  air_time
  )

출력된 flights_sml에  gain(dep_delay - arr_delay)와 speed(distance / air_time)변수를 새로 만들어서 추가해보자.

flights_sml %>% mutate(gain = dep_delay - arr_delay,
   speed = distance / air_time * 60)

 

flights_sml %>% mutate(gain = dep_delay - arr_delay,
   speed = distance / air_time * 60,
   gain_per_hour = gain / hours)

추가한 변수를 즉시 이용도 가능하다.

 

5. transmute()

새로 만든 변수들만으로 새로운 dataframe을 만들고 싶다면 transmute()함수를 사용할 수 있다.

transmute(flights,
  gain = dep_delay - arr_delay,
  hours = air_time / 60,
  gain_per_hour = gain / hours)

- transmute()에서의 %/%, %% 연산자 활용

transmute(flights, 
  dep_time, 
  hour = dep_time %/% 100,
  minute = dep_time %% 100
  )

%/%와 %% 연산을 사용하면 정수를 몫과 나머지로 나눌 수 있기 때문에, dep_time에서 시간과 분을 계산할 수 있다.

 

- lag(), lead()함수

lag()와 lead()함수를 사용하여 이전 행의 값과, 이후 행의 값을 뽑을 수 있다. 어떤 상황에서 사용되는지에 대해서는 나중을 기약하겠다..

 

(x <- 1:10)
lag(x)
lead(x)

- cumsum(), cummean()  : 누적합, 누적비율

(x <- 1:10)
cumsum(x)
cummean(x)

- rank

rank를 매기는 다양한 함수들이 있지만 min_rank부터 시작하도록하자.

y <- c(1, 2, 2, NA, 3, 4)
min_rank(y)

작은 순서대로 벡터별 순서를 반환한다.

반환될 값은 1 2 2 NA 4 5. 으로 NA에 대해서는 역시 순서또한 NA로 반환한다. 이전에 NA는 모든 연산에 대해서 NA를 반환하는 특징이 있다고 배웠음.

 

만약 작은 순서가 아닌 큰 순서대로 rank를 반환하고 싶다면, desc()를 사용하면 된다.

min_rank(desc(y))

5 3 3 NA 2 1을 반환한다.

 

Exercise

1. dep_time과 sched_dep_time에 대해서 분단위로 표시하기

flights %>% transmute(dep_hour = dep_time %/% 100,
  dep_minute = dep_time %% 100 + dep_hour *60,
  sched_hour = sched_dep_time %/% 100,
  sched_minute = (sched_dep_time %/% 100)+ (sched_hour *60))

2. air_time 과 arr_time- dep_time을 비교하고, 어떤 값을 예상할 수 있는지. 어떤 해결책이 있는지 구안하기.

 

air_time의 경우 분단위의 숫자의 value을 갖고, arr_time과 dep_time은 HHMM의 숫자 표기를 쓴다. 즉 HHMM의 숫자표기를 분다위로 바꾸어 새로운 변수로 만든뒤 air_time과 비교하면 같은 값을 예상할 수 있을 것 같다.

flights %>% 
  mutate(dep_time = (dep_time %/% 100) * 60 + (dep_time %%100),
         sched_dep_time = (sched_dep_time %/% 100)*60 + (sched_dep_time %%100),
         arr_time = (arr_time%/%100)*60 + (arr_time%%100),
         sched_arr_time = (sched_arr_time %/% 100)*60 + (sched_arr_time %%100)) %>% 
  transmute((arr_time-dep_time)%%(60*24) - air_time)

여기서 마지막에 transmute하는 과정에서 %%(60*24) 연산에 대한 궁금증을 갖고 있는 사람들이 분명있을 것이다.. 나처럼 수학적 베이스가 좀 약한..ㅎㅎ

"modulo 24 hours"이라는 연산인데.

예를 들어, 한 비행기가 밤 11시에 출발하여 다음날 아침 1시에 도착했다고 가정해 보자. 이 경우 arr_time - dep_time을 계산하면 -22시간이 나온다. 이 시간을 실제 비행 시간이라고 할 수 없을 것이다. 이 문제를 해결하기 위해 %% (60*24) 연산을 사용하여 결과를 24시간 범위 내로 맞춘다. 

 

 

3. dep_time과 sched_dep_time, dep_delay를 비교하고, 세 개의 숫자가 갖는 관계에 대해서 예상해보자.

 

상식선에서 우리는 sched_dep_time 과 dep_delay를 합치면 dep_time이 나올 것이라고 예상할 수 있다.

flights %>% 
  mutate(dep_time = (dep_time %/% 100) * 60 + (dep_time %% 100),
         sched_dep_time = (sched_dep_time %/% 100) * 60 + (sched_dep_time %% 100),
         arr_time = (arr_time %/% 100) * 60 + (arr_time %% 100),
         sched_arr_time = (sched_arr_time %/% 100) * 60 + (sched_arr_time %% 100)) %>%
  transmute(near((sched_dep_time + dep_delay) %% (60*24), dep_time, tol=1))

 

4. 가장 delayed된 flights을 ranking 함수를 사용해라 찾고, 동점을 어떻게 처리할 것인지 찾아보자.

우선 이 데이터에서 delayed된 데이터는 dep, arr 모두에서 존재하지 않는다.

flights %>% 
  filter(min_rank(desc(dep_delay)) <= 10) %>% 
  mutate(rank = min_rank(desc(dep_delay))) %>% 
  select(dep_delay, rank) %>% arrange(rank)

filter(flights, min_rank(desc(dep_delay))<=10)
flights %>% top_n(n = 10, wt = dep_delay) %>% arrange(desc(dep_delay))

두 가지 방법 모두 most delayed top10을 찾을 수 있는 코드이다.

 

두 번째 코드의 경우 top_n()함수를 사용했다.

 

 

5. 1:3 + 1:10은 무엇을 반환하는가?

1:3 + 1:10

10개짜리 벡터를 반환함과 동시에 warning message를 받게 된다. 이유는 3개 짜리 벡터와 10개 짜리 벡터를 더하는 행위를 시도한 것이기 때문이다. 하지만 automatic vector extension으로 자동으로 벡터확장이 되어 연산처리가 가능해진다.