트위티의 열하일기

3. 데이터 가공, 정제하기 본문

Programming Languages/R

3. 데이터 가공, 정제하기

예지레슬리초이 2025. 2. 13. 15:46

# 데이터 가공하기

데이터를 추출하고, 여러 데이터를 합치기 위해 데이터 전처리 작업에 가장 많이 사용되는 R 패키지, dplyr을 사용한다.

 

1. filter() 함수로 원하는 데이터 추출

# exam 에서 class가 1인 경우만 추출해서 출력

exam %>% filter(class == 1)

# 결과
  id class math english science
1  1     1   50      98      50
2  2     1   60      97      60
3  3     1   45      86      78
4  4     1   30      98      58
  • %>% 기호를 활용하여 함수들을 나열하는 방식으로 코드 작성
  • filter() 에 조건을 입력하면 조건에 해당되는 행만 추출

- 특정 조건에 부합하는 데이터만 추출하기

exam %>% filter(math > 50)

#결과
   id class math english science
1   2     1   60      97      60
2   7     2   80      90      45
3   8     2   90      78      25
4  11     3   65      65      65
5  15     4   75      56      78
6  16     4   58      98      65
7  17     5   65      68      98
8  18     5   80      78      90
9  19     5   89      68      87
10 20     5   78      83      58

 

- 여러 조건을 충족하는 행 추출하기

exam %>% filter(class == 1 & math >= 50)

# 결과
  id class math english science
1  1     1   50      98      50
2  2     1   60      97      60

exam %>% filter(math >=90 | english >= 90)

# 결과
  id class math english science
1  1     1   50      98      50
2  2     1   60      97      60
3  4     1   30      98      58
4  7     2   80      90      45
5  8     2   90      78      25
6  9     3   20      98      15
7 10     3   50      98      45
8 13     4   46      98      65
9 16     4   58      98      65

 

- %in% 기호: 변수의값이 지정한 조건 목록에 해당하는지 확인하는 기능

exam %>% filter(class %in% c(1, 3, 5)) # 1, 3, 5반에 해당하면 추출

# 결과
   id class math english science
1   1     1   50      98      50
2   2     1   60      97      60
3   3     1   45      86      78
4   4     1   30      98      58
5   9     3   20      98      15
6  10     3   50      98      45
7  11     3   65      65      65
8  12     3   45      85      32
9  17     5   65      68      98
10 18     5   80      78      90
11 19     5   89      68      87
12 20     5   78      83      58

 

- 추출한 행으로 새로운 데이터 만들기 (using '<-')

class1 <- exam %>% filter(class == 1)
class2 <- exam %>% filter(class == 2)

# class1  
  id class math english science
1  1     1   50      98      50
2  2     1   60      97      60
3  3     1   45      86      78
4  4     1   30      98      58

#class2
  id class math english science
1  5     2   25      80      65
2  6     2   50      89      98
3  7     2   80      90      45
4  8     2   90      78      25

mean(class1$math)
## [1] 46.25

mean(class2$math)
## [1] 61.25

 

2. select() 함수로 데이터 속 수많은 변수중 일부 변수만 추출해서 사용

exam %>% select(math) %>%
	head # 6개만 출력 

#결과
  math
1   50
2   60
3   45
4   30
5   25
6   50

 

- 여러 변수 추출하기

exam %>% select(class, math, english)

 

- 변수 제외하기, 여러 변수 제외하기

exam %>% select(-math) # math를 제외한 나머지 모든 변수 추출

 

3. '%>%' 기호를 활용하여 dplyr의 함수들 조합

# class가 1인 행만 추출한 다음 english 추출
exam %>% filter(class==1) %>%
	select(english)
         
# 결과
  english
1      98
2      97
3      86
4      98

*%>%로 코드가 연결되는 부분에서 줄을 바꾸면 함수별로 구분되기 때문에 가독성 있는 코드 구현 가능

 

4. arrange() 함수를 활용하여 원하는 순서대로 정렬하기 

- 오름차순으로 정렬 (default)

exam %>% arrange(math) # math 점수가 낮은 사람에서 높은 사람 순으로 오름차순 정렬

# 결과
   id class math english science
1   9     3   20      98      15
2   5     2   25      80      65
3   4     1   30      98      58
4   3     1   45      86      78
5  12     3   45      85      32
6  13     4   46      98      65
7  14     4   48      87      12
8   1     1   50      98      50
9   6     2   50      89      98
10 10     3   50      98      45
11 16     4   58      98      65
12  2     1   60      97      60
13 11     3   65      65      65
14 17     5   65      68      98
15 15     4   75      56      78
16 20     5   78      83      58
17  7     2   80      90      45
18 18     5   80      78      90
19 19     5   89      68      87
20  8     2   90      78      25

 

- 내림차순으로 정렬: desc() 에 적용

exam %>% arrange(desc(math)) # math 내림차순 정렬

#결과
   id class math english science
1   8     2   90      78      25
2  19     5   89      68      87
3   7     2   80      90      45
4  18     5   80      78      90
5  20     5   78      83      58
6  15     4   75      56      78
7  11     3   65      65      65
8  17     5   65      68      98
9   2     1   60      97      60
10 16     4   58      98      65
11  1     1   50      98      50
12  6     2   50      89      98
13 10     3   50      98      45
14 14     4   48      87      12
15 13     4   46      98      65
16  3     1   45      86      78
17 12     3   45      85      32
18  4     1   30      98      58
19  5     2   25      80      65
20  9     3   20      98      15

 

- 정렬 기준으로 삼을 변수 여러 개 지정하기

# 먼저 class를 기준으로 오름차순 정렬한 후 math 점수를 기준으로 오름차순 정렬해서 출력
exam %>% arrange(class, math) %>%
	head
    
# 결과
> exam %>% arrange(class, math)
   id class math english science
1   4     1   30      98      58
2   3     1   45      86      78
3   1     1   50      98      50
4   2     1   60      97      60
5   5     2   25      80      65
6   6     2   50      89      98

 

5. mutate()함수를 사용하여 파생변수 만들어 추가

# total이라는 총합 변수를 추가
exam %>% mutate(total = math + english + science) %>%
	head
    
# 결과
  id class math english science total
1  1     1   50      98      50   198
2  2     1   60      97      60   217
3  3     1   45      86      78   209
4  4     1   30      98      58   186
5  5     2   25      80      65   170
6  6     2   50      89      98   237


exam %>% mutate(total = math + english + science) %>% # total 변수 추가
	arrange(total) %>% # total 변수를 기준으로 정렬
    head
    
# 결과
  id class math english science total
1  9     3   20      98      15   133
2 14     4   48      87      12   147
3 12     3   45      85      32   162
4  5     2   25      80      65   170
5  4     1   30      98      58   186
6  8     2   90      78      25   193

 

- 여러 파생변수 한 번에 추가하기

exam %>% mutate(total = math + english + science,
				mean = (math + english + science)/3) %>%
                head

# 결과
  id class math english science total     mean
1  1     1   50      98      50   198 66.00000
2  2     1   60      97      60   217 72.33333
3  3     1   45      86      78   209 69.66667
4  4     1   30      98      58   186 62.00000
5  5     2   25      80      65   170 56.66667
6  6     2   50      89      98   237 79.00000

 

- mutate()에 ifelse() 조건문 적용하기

exam %>% mutate(test = ifelse(science >= 60, "pass", "fail")) %>%
	head
    
# 결과
  id class math english science test
1  1     1   50      98      50 fail
2  2     1   60      97      60 pass
3  3     1   45      86      78 pass
4  4     1   30      98      58 fail
5  5     2   25      80      65 pass
6  6     2   50      89      98 pass

 

- dplyr 함수의 mutate()를 사용하는 것운 기본 문법을 이용하여 파생변수를 추가하는 것과 비교했을 때, 변수명 앞에 데이터 프레임명을 반복해 입력하지 않아도 된다는 점에서 간편하다.

 

6. group_by()와 summarise()를 사용하여 요약표를 만들기

- summarise()

exam %>% summarise(mean_math = mean(math))

# 결과
  mean_math
1     57.45

→ summarise()는 전체를 요약한 값을 구하기보다는 group_by() 와 조합해 집단별 요약표를 만들 때 사용

 

- group_by()

: group_by()에 변수를 지정하면 변수 항목별로 데이터를 분리한다. 여기에 summarise()를 조합하면 집단별 요양 통계량을 산출한다.

exam %>%
+ group_by(class) %>%
+ summarise(mean_math = mean(math))

# A tibble: 5 × 2 -> 데이터가 5행 2열의 tibble 형태라는 것을 의미
  class mean_math
  <int>     <dbl>
1     1      46.2
2     2      61.2
3     3      45  
4     4      56.8
5     5      78

*group_by()는 출력 결과를 데이터 프레임의 업그레이드 버전인 tibble 형태로 만듦

 

- 여러 통계량 한 번에 산출하기

exam %>%
  group_by(class) %>%
  summarise(mean_math = mean(math),
            sum_math = sum(math),
            median_math = median(math),
            n = n()) # 학생수


 # A tibble: 5 × 5
  class mean_math sum_math median_math     n
  <int>     <dbl>    <int>       <dbl> <int>
1     1      46.2      185        47.5     4
2     2      61.2      245        65       4
3     3      45        180        47.5     4
4     4      56.8      227        53       4
5     5      78        312        79       4

 

- group_by()에 여러 변수를 지정 → 각 집단별로 다시 집단 나눌 수 있음

 (e.g. 성적 데이터를 반별로 나눈 후 다시 성별로 나눠 각 반의 성별 평균 점수 구하기)

mpg %>%
  group_by(manufacturer, drv) %>% # 회사별, 구동방식별 분리리
  summarise(mean_cty = mean(cty)) %>% #cty 평균 산출출
  head(10)
  
  # A tibble: 10 × 3
# Groups:   manufacturer [5]
   manufacturer drv   mean_cty
   <chr>        <chr>    <dbl>
 1 audi         4         16.8
 2 audi         f         18.9
 3 chevrolet    4         12.5
 4 chevrolet    f         18.8
 5 chevrolet    r         14.1
 6 dodge        4         12  
 7 dodge        f         15.8
 8 ford         4         13.3
 9 ford         r         14.8
10 honda        f         24.4

 

- dplyr 조합하기

: 회사별로 "suv" 자동차의 도시 및 고속도로 통합연비 평균을 구해 내림차순으로 정렬하고, 1~5위까지 출력하기

mpg %>%
  group_by(manufacturer) %>%
  filter(class=="suv") %>%
  mutate(tot = (cty+hwy)/2) %>%
  summarise(mean_tot = mean(tot)) %>%
  arrange(desc(mean_tot)) %>%
  head(5)

# A tibble: 5 × 2
  manufacturer mean_tot
  <chr>           <dbl>
1 subaru           21.9
2 toyota           16.3
3 nissan           15.9
4 mercury          15.6
5 jeep             15.6

 

7. 데이터 합치기

- left_join() 함수를 활용하여 데이터를 가로로 합치기

 

test1 <- data.frame(id = c(1, 2, 3, 4, 5),
                    midterm = c(60, 80, 70, 90, 85))
test2 <- data.frame(id = c(1, 2, 3, 4, 5),
                    final = c(70, 83, 65, 95, 80))
                    
total <- left_join(test1, test2, by = "id") # id를 기준으로 합쳐 total에 할당
total

# 결과
  id midterm final
1  1      60    70
2  2      80    83
3  3      70    65
4  4      90    95
5  5      85    80

 

- bind_rows() 함수를 활용하여 데이터를 세로로 합치기

group_a <- data.frame(id = c(1, 2, 3, 4, 5),
					  test = c(60, 80, 70, 90, 85))
                      
group_b <- data.frame(id = c(6, 7, 8, 9, 10),
					  test = c(60, 80, 70, 90, 85))                     

group_all <- bind_rows(group_a, group_b)
group_all

# 결과
   id test
1   1   60
2   2   80
3   3   70
4   4   90
5   5   85
6   6   60
7   7   80
8   8   70
9   9   90
10 10   85

 

 

# 데이터 정제 - 빠진 데이터, 이상한 데이터 제거

1. 결측치 정제하기

결측치란, Missing value, 즉 비어 잇는 값을 의미한다. 결측치가 있으면 함수가 적용되지 않거나 분석 결과가 왜곡되는 문제가 발생한다.

 

a. 결측치 찾기

- is.na() 함수 활용 → TRUE/FALSE 값을 반환

df <- data.frame(sex = c("M", "F", NA, "M", "F"),
                 score = c(5, 4, 3, 4, NA))

is.na(df)

# 결과
       sex score
[1,] FALSE FALSE
[2,] FALSE FALSE
[3,]  TRUE FALSE
[4,] FALSE FALSE
[5,] FALSE  TRUE

 

- is.na()를 table() 함수에 적용: 데이터에 결측치가 총 몇 개 있는지 출력

table(is.na(df))

# 결과
FALSE  TRUE 
    8     2

 

- table(is.na())에 변수명을 지정: 해당 변수에 결측치가 몇 개 있는지 알 수 있음

table(is.na(df$sex))

# 결과
FALSE  TRUE 
    4     1 
    
table(is.na(df$score))

# 결과
FALSE  TRUE 
    4     1

 

- 결측치가 포함된 데이터를 함수에 적용하면 정상적으로 연산되지 않고 NA가 출력됨

 

b. 결측치 제거하기

- is.na()를 filter()에 적용 → 결측치가 있는 행 제거

# 결측치 있는 데이터만 표시
df %>% filter(is.na(score))

#결과
  sex score
1   F    NA

# score 결측치를 제외한 행 추출
df %>% filter(!is.na(score))

#결과
   sex score
1    M     5
2    F     4
3 <NA>     3
4    M     4

 

- & 기호를 활용하여 여러 변수 동시에 결측치 없는 행 추출

df_nomiss <- df %>% filter(!is.na(score) & !is.na(sex))
df_nomiss

# 결과
  sex score
1   M     5
2   F     4
3   M     4

 

- na.omit(): 결측치가 하나라도 있으면 제거하기

df_nomiss2 <- na.omit(df)
df_nomiss2

# 결과
  sex score
1   M     5
2   F     4
4   M     4
  • 간편하지만, 분석에 필요한 행까지 손실된다는 단점 존재
  • 따라서 filter()를 이용해 분석에 사용할 변수의 결측치만 제거하는 방식 권장

c. na.rm 파라미터 (NA Remove): 결측치를 제외하고 연산하도록 설정

- na.rm을 TRUE로 설정하면 결측치를 제외하고 함수를 적용함

- 그러나 모든 함수가 na.rm을 지원하지는 않음

- 만약 na.rm을 지원하지 않는 함수라면 filter()로 결측치를 제거한 후 함수를 적용해야 함

mean(df$score, na.rm = T) # 결측치 제외하고 평균 산출
sum(df$score, na.rm=T) # 결측치 제외하고 합계 산출

 

d. 결측치 대체하기

데이터가 작고 결측치가 많은 경우, 결측치를 제거하면 너무 많은 데이터가 손실돼 분석 결과가 왜곡되는 문제가 발생한다. 이때 결측치 대체법 (Imputation)을 수행하면, 결측치가 다른 값으로 대체되어 분석 결과가 왜곡되는 문제를 보완할 수 있다.

- 평균, 최빈값 같은 대표값을 구해 모든 결측치의 하나의 값으로 일괄 대체하는 방법

- 통계 분석 기법으로 각 결측치의 예측값을 추정해 대체하는 방법

 

- 평균값으로 결측치 대체하기

exam $math <- ifelse(is.na(exam$math), 55, exam$math) # math가 NA면 55로 대체

table(is.na(exam$math)) # 결측치 빈도표를 생성하여 잘 대체되었는지 확인

 

2. 이상치 정제하기

이상치란, 정상 범주에서 크게 벗어난 값이다. 데이터 수집 과정에서 오류가 발생할 수도 있고, 오류는 아니지만 굉장히 드물게 발생하는 극단적인 값이 있을 수도 있다. 이때, 분석 결과가 왜곡될 수 있기 때문에 이상치를 제거하는 작업이 필요하다.

 

a. 논리적으로 존재할 수 없는 값

- e.g. sex 변수에 3이라는 value가 들어 있음, 1~5점을 지닐 수 있는 score 변수에 6이라는 value가 들어 있음

outlier <- data.frame(sex = c(1, 2, 1, 3, 2, 1),
					  score = c(5, 4, 3, 4, 2, 6))

outlier

# 결과
  sex score
1   1     5
2   2     4
3   1     3
4   3     4
5   2     2
6   1     6

 

- 이상치 확인하기 → table()을 통해 빈도표 생성

table(outlier$sex)

#결과
1 2 3 
3 2 1 

table(outlier$score)

#결과
2 3 4 5 6 
1 1 2 1 1

 

- 결측 처리하기 → ifelse()를 이용해 이상치일 경우 NA를 부여

이상치를 결측치로 변환하는 작업

outlier$sex <- ifelse(outlier$sex == 3, NA, outlier$sex)
outlier

# 결과
  sex score
1   1     5
2   2     4
3   1     3
4  NA     4
5   2     2
6   1     6

outlier$score <- ifelse(outlier$score == 6, NA, outlier$score)
outlier

# 결과
  sex score
1   1     5
2   2     4
3   1     3
4  NA     4
5   2     2
6   1    NA

 

- filter()을 이용해 결측치 제외 후 score 평균 구하기

outlier %>%
+   filter(!is.na(sex) & !is.na(score)) %>%
+   group_by(sex) %>%
+   summarise(mean_score = mean(score))

# A tibble: 2 × 2
    sex mean_score
  <dbl>      <dbl>
1     1          4
2     2          3

 

b. 극단적인 값 (극단치)

- 방법 1) 어디까지를 정상 범위로 볼 것인지 정하고, 이 범위를 벗어나면 극단치로 간주

- 방법 2) 통계적인 기준을 이용하여 (e.g. boxplot) 중심에서 크게 벗어난 값을 극단치로 간주

 

- 상자 그림으로 극단치 기준 정하기

# mpg 데이터의 hwy 변수로 boxplot 그림 만들기
boxplot(mpg$hwy)

 

 

- 결측 처리하기

mpg$hwy <- ifelse(mpg$hwy < 12 | mpg$hwy > 37, NA, mpg$hwy)
table(is.na(mpg$hwy))

# 결과
FALSE  TRUE 
  231     3

 

- 결측치를 제외하고 간단한 분석 수행

mpg %>%
+   group_by(drv) %>%
+   summarise(mean_hwy = mean(hwy, na.rm = T))

# A tibble: 3 × 2
  drv   mean_hwy
  <chr>    <dbl>
1 4         19.2
2 f         27.7
3 r         21