외부 텍스트 파일로 대용량의 데이터 셋을 R로 불러들이고 나면 가장 먼저 하는 것이 str()로 데이터 구조 파악하기, head(), tail()로 데이터 몇 개 미리보기, 그 다음에 하는 것이 바로 결측값 확인 및 처리, 특이값/영향치 확인 및 처리 등의 탐색적 데이터 분석입니다.

 

R에서 결측값이 들어있는 상태에서 통계 분석을 진행하면 NA 라는 결과가 나올 뿐, 원하는 결과를 얻지 못합니다.  그리고 대부분의 R 통계 함수에는 옵션으로 "na.rm = TRUE" 라는 옵션을 제공해서 결측값을 통계량 계산할 때 포함하지 말지를 선택할 수 있게 해줍니다.

 

 

이번 포스팅에서는 데이터 셋에 (1) 결측값이 포함되어 있는지 확인하는 방법, (2) 결측값이 들어있다면 결측값을 제거하는 방법, (3) 결측값을 다른 값으로 대체하는 방법에 대해서 알아보도록 하겠습니다.

 

 

 

 

 R 결측값 확인 및 처리: is.na(), na.omit(), complete.cases()

 

 

(1) 결측값이 포함되어 있는지 확인하는 방법: is.na()

 

> x <- c(1, 2, 3, 4, NA, 6, 7, 8, 9, NA)
> is.na(x)
 [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE

 

위의 벡터처럼 구성요소 갯수가 몇 개 안될 경우 is.na() 한 후에 TRUE, FALSE 논리형 값을 눈으로 보고 확인할 수 있습니다. 하지만 아래의 Cars93 데이터 프레임처럼 변수 갯수도 많고, 관측치 갯수도 많은 경우 (대부분의 실무에서 쓰는 데이터 셋은 이처럼 변수도 많고 관측치도 많지요) is.na() 함수만 가지고서는 아무래도 결측치 현황을 파악하는데 무리가 있습니다.

 

 

> library(MASS) > is.na(Cars93) Manufacturer Model Type Min.Price Price Max.Price MPG.city MPG.highway AirBags DriveTrain Cylinders EngineSize 1 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 2 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 3 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 4 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 5 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 6 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 7 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 8 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 9 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 10 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 11 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 12 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 13 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 14 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 15 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 16 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 17 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 18 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE 19 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

..... (뒤에 계속 있으며, 너무 많아서 이쯤에서 중략) .....

 

 

(2) 결측값이 총 몇 개인지 계산하는 방법: sum(is.na())

 

> sum(is.na(x))
[1] 2

 

 

 

> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
 

> # Cars93 데이터 프레임에 결측값 갯수 총 합계 구하기

> sum(is.na(Cars93))
[1] 13
> 
> # Cars93 의 각 변수별로 결측값 개수 구하기 (27개 중에서 4개만 예시로 함)
> sum(is.na(Cars93$Manufacturer)) 
[1] 0
> sum(is.na(Cars93$Price))
[1] 0
> sum(is.na(Cars93$Rear.seat.room))
[1] 2
> sum(is.na(Cars93$Luggage.room))
[1] 11

 

sum(is.na()) 함수를 이용하니 변수가 많거나 관측값이 많은 경우도 결측값 현황을 금방 파악할 수 있습니다.  R은 TRUE 를 '1'로, FALSE 를 '0'으로 인식하기 때문에 sum(is.na())를 하게 되면 TRUE 값을 '1'로 해서 합계를 내기 때문에 가능한 함수가 되겠습니다.



colSums() 함수를 사용하면 데이터 프레임 내 다수 변수들에 대해서 한번에 각 개별 변수별 결측값의 개수 합계를 구할 수 있습니다. 바로 위에서 개별 함수별로 일일이 sum(is.na(Cars93$Manufacturer))...이런 식으로 변수의 개수만큼 쓰는 것을 colSums() 함수로는 한줄이면 해결할 수 있으니 훨씬 편합니다. 


> colSums(is.na(Cars93))

      Manufacturer              Model               Type          Min.Price              Price 

                 0                  0                  0                  0                  0 

         Max.Price           MPG.city        MPG.highway            AirBags         DriveTrain 

                 0                  0                  0                  0                  0 

         Cylinders         EngineSize         Horsepower                RPM       Rev.per.mile 

                 0                  0                  0                  0                  0 

   Man.trans.avail Fuel.tank.capacity         Passengers             Length          Wheelbase 

                 0                  0                  0                  0                  0 

             Width        Turn.circle     Rear.seat.room       Luggage.room             Weight 

                 0                  0                  2                 11                  0 

            Origin               Make 

                 0                  0  


 

 

(3) 결측값을 통계 분석 시 제외(미포함): na.rm = TRUE

 

> sum(x)
[1] NA
> mean(x)
[1] NA
> sum(x, na.rm = TRUE)
[1] 40
> mean(x, na.rm = TRUE)
[1] 5

 

결측값이 들어있는 벡터에 대해서 sum(), mean(), sd(), min(), max(), range() 등의 통계 함수를 적용하면 'NA'만 나오게 됩니다. 결측값을 포함하지 말고 통계 함수 계산을 하라는 옵션이 na.rm = TRUE 가 되겠습니다.

 

> sum(Cars93$Luggage.room)
[1] NA
> mean(Cars93$Luggage.room)
[1] NA
> 
> sum(Cars93$Luggage.room, na.rm = TRUE)
[1] 1139
> mean(Cars93$Luggage.room, na.rm = TRUE)
[1] 13.89024

 

위 예제는 결측값이 포함된 데이터 프레임의 특정 변수에 대해 indexing을 해서 통계 함수를 적용해본 경우 입니다. 역시 na.rm = TRUE 옵션을 설정해 주어야 통계 계산이 제대로 됨을 알 수 있습니다.  na.rm =  FALSE 가 디폴트이다보니 na.rm=TRUE를 설정하지 않는 경우 결측값이 포함되어 있으면 NA가 결과로 나타나게 됩니다.

 

 

 

(4) 결측값이 들어있는 행 전체를 데이터 셋에서 제거: na.omit()

 

 na.rm = TRUE 옵션은 원래의 데이터 셋은 그대로 둔채 통계량 계산할 때만 포함하지 않게 됩니다. 따라서 다수의 통계 함수 혹은 다수의 변수에 통계 함수를 사용해야 하는 경우 매번 na.rm = TRUE 옵션을 설정해주는게 번거로울 수 있겠지요?  차라리 원래 데이터 셋에서 결측값을 제거해버리면 되겠다는 생각이 드셨을 겁니다.

 

결측값이 들어있는 행을 통째로 무식하게 제거하는 함수가 na.omit()이며, 좀더 예리하게 특정 행과 열을 지정해서 그곳에 결측값이 있는 경우만 메스로 정밀 수술하는 함수가 complete.cases()가 되겠습니다.

 

> Cars93_1 <- na.omit(Cars93)
> str(Cars93_1)
'data.frame':	82 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:11] 16 17 19 26 36 56 57 66 70 87 ...
  .. ..- attr(*, "names")= chr [1:11] "16" "17" "19" "26" ..

 

 

처음에 Cars93이  "'data.frame': 93 obs. of  27 variables:", 즉 93개의 관측치가 있었는데요,

na.omit(Cars93) 함수를 적용한 후에 Cars93_1 이라는 이름으로 새로 저장해서 str()로 데이터 구조를 보니 "'data.frame': 82 obs. of  27 variables:", 즉 82개 관측치로 총 11개 관측치가 줄어들었음을 알 수 있습니다. 결측값이 하나라도 들어있는 행 11개가 통째로 삭제되어 버렸기 때문입니다.

 

위의 예처럼 결측값이 들어있는 행을 통째로 삭제할 때는 만약의 사태를 대비해서 원본은 그대로 유지하고, 행을 삭제한 데이터 셋을 별도의 이름으로 저장해서 분석을 진행하는 것을 추천합니다.

 

 

 

(5) 특정 행과 열에 결측값이 들어있는 행을 데이터 셋에서 제거 : complete.cases()

 

> sum(is.na(Cars93))
[1] 13
> 

> # Cars93 데이터 프레임의 "Rear.seat.room" 칼럼 내 결측값이 있는 행 전체 삭제

> Cars93_2 <- Cars93[ complete.cases(Cars93[ , c("Rear.seat.room")]), ] > sum(is.na(Cars93_2)) [1] 9 >

> # Cars93 데이터 프레임의 23~24번째 칼럼 내 결측값이 있는 행 전체 삭제

> Cars93_3 <- Cars93[ complete.cases(Cars93[ , c(23:24)]), ] > sum(is.na(Cars93_3)) [1] 0 

> 
> dim(Cars93_3) # 관측값이 82개로서 11개 줄어듬
[1] 82 27

> str(Cars93_3) # 관측값이 82개로서 11개 줄어듬 'data.frame': 82 obs. of 27 variables: $ Manufacturer : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ... $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ... $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ... $ Min.Price : num 12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ... $ Price : num 15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ... $ Max.Price : num 18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ... $ MPG.city : int 25 18 20 19 22 22 19 16 19 16 ... $ MPG.highway : int 31 25 26 26 30 31 28 25 27 25 ... $ AirBags : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ... $ DriveTrain : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ... $ Cylinders : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ... $ EngineSize : num 1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ... $ Horsepower : int 140 200 172 172 208 110 170 180 170 200 ... $ RPM : int 6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ... $ Rev.per.mile : int 2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ... $ Man.trans.avail : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ... $ Fuel.tank.capacity: num 13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ... $ Passengers : int 5 5 5 6 4 6 6 6 5 6 ... $ Length : int 177 195 180 193 186 189 200 216 198 206 ... $ Wheelbase : int 102 115 102 106 109 105 111 116 108 114 ... $ Width : int 68 71 67 70 69 69 74 78 73 73 ... $ Turn.circle : int 37 38 37 37 39 41 42 45 41 43 ... $ Rear.seat.room : num 26.5 30 28 31 27 28 30.5 30.5 26.5 35 ... $ Luggage.room : int 11 15 14 17 13 16 17 21 14 18 ... $ Weight : int 2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ... $ Origin : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ... $ Make : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 

 

(6) 결측값을 다른 값으로 대체: dataset$var[is.na(dataset$var)] <- new_value

 

> Cars93$Luggage.room [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14 NA 16 11 11 15 12 12 13 12 18 NA 18 [38] 21 10 11 8 12 14 11 12 9 14 15 14 9 19 22 16 13 14 NA NA 12 15 6 15 11 14 12 14 NA 14 14 16 NA 17 8 17 13 [75] 13 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15 > sum(is.na(Cars93$Luggage.room)) [1] 11

> 

> # Luggage.room 변수 내 결측값을 '0'으로 대체

> Cars93_4 <- Cars93
> Cars93_4$Luggage.room[is.na(Cars93_4$Luggage.room)] <- 0
> Cars93_4$Luggage.room
 [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16  0  0 20  0 15 14 17 11 13 14  0 16 11 11 15 12 12 13 12 18  0 18
[38] 21 10 11  8 12 14 11 12  9 14 15 14  9 19 22 16 13 14  0  0 12 15  6 15 11 14 12 14  0 14 14 16  0 17  8 17 13
[75] 13 16 18 14 12 10 15 14 10 11 13 15  0 10  0 14 15 14 15

 

 

 

sum(is.na(Cars93$Luggage.room)) 함수를 사용해 Cars93의 Luggage.room 변수에 보면 11개의 결측값이 있음을 알 수 있습니다.  Luggage.room 변수 내 결측값을 indexing 기법을 활용해서 '0'로 대체하는 방법이 위의 예제가 되겠습니다.

 

물론 '0'이 아니라 다른 값으로도 대체가 가능합니다.  아래의 예제에서는 결측값을 미포함(na.rm = TRUE)했을 때의 Luggage.room의 평균값으로 결측값을 대체하여 보도록 하겠습니다.

 

> Cars93_5 <- Cars93
> Cars93_5$Luggage.room[is.na(Cars93_5$Luggage.room)] <- mean(Cars93_5$Luggage.room, na.rm = TRUE)
> sum(is.na(Cars93_5$Luggage.room))
[1] 0
> Cars93_5$Luggage.room
 [1] 11.00000 15.00000 14.00000 17.00000 13.00000 16.00000 17.00000 21.00000 14.00000 18.00000 14.00000 13.00000
[13] 14.00000 13.00000 16.00000 13.89024 13.89024 20.00000 13.89024 15.00000 14.00000 17.00000 11.00000 13.00000
[25] 14.00000 13.89024 16.00000 11.00000 11.00000 15.00000 12.00000 12.00000 13.00000 12.00000 18.00000 13.89024
[37] 18.00000 21.00000 10.00000 11.00000  8.00000 12.00000 14.00000 11.00000 12.00000  9.00000 14.00000 15.00000
[49] 14.00000  9.00000 19.00000 22.00000 16.00000 13.00000 14.00000 13.89024 13.89024 12.00000 15.00000  6.00000
[61] 15.00000 11.00000 14.00000 12.00000 14.00000 13.89024 14.00000 14.00000 16.00000 13.89024 17.00000  8.00000
[73] 17.00000 13.00000 13.00000 16.00000 18.00000 14.00000 12.00000 10.00000 15.00000 14.00000 10.00000 11.00000
[85] 13.00000 15.00000 13.89024 10.00000 13.89024 14.00000 15.00000 14.00000 15.00000

 

 

 

 

 

(7) 데이터프레임의 모든 행의 결측값을 특정 값(가령, '0')으로 일괄 대체 

: dataset[is.na(dataset)] <- 0

 

> Cars93_6 <- Cars93
> # counting the number of missing values in Cars93 dataset
> sum(is.na(Cars93_6)) # 13
[1] 13
> 
> # converting the missing value in Cars93 dataframe to '0'
> Cars93_6[is.na(Cars93_6)] <- 0
> 
> # counting the number of missing values in Cars93 dataset
> sum(is.na(Cars93_6)) # 0
[1] 0

 

 

 

(8) 데이터프레임의 각 변수의 결측값을 각 변수 별 평균값으로 일괄 대체

: sapply(dataset, function(x) ifelse(is.na(x), mean(x, na.rm=TRUE), x)) 

 

위의 (6)번에서 결측값을 그 열의 평균으로 대체하는 방법을 소개했는데요, 만약 결측값을 포함한 열이 매우 많다면 일일이 변수이름을 지정해가면서 나열해서 입력하기가 번거롭습니다. 이럴 때 sapply() 함수를 사용하면 일괄로 결측값을 포함한 변수에 대해서는 해당 변수의 평균으로 대체하라고 프로그래밍할 수 있습니다. sapply()를 적용하면 matrix를 반환하므로 dataframe으로 만들기 위해서 앞에 data.frame() 을 추가로 붙여주었습니다.

 

 

> # converting the missing value in Cars93 dataframe to each column's mean value
> Cars93_7 <- Cars93[1:20,c("Rear.seat.room", "Luggage.room")]
> 
> colSums(is.na(Cars93_7))
Rear.seat.room   Luggage.room 
             1              3 
> 
> Cars93_7
   Rear.seat.room Luggage.room
1            26.5           11
2            30.0           15
3            28.0           14
4            31.0           17
5            27.0           13
6            28.0           16
7            30.5           17
8            30.5           21
9            26.5           14
10           35.0           18
11           31.0           14
12           25.0           13
13           26.0           14
14           25.0           13
15           28.5           16
16           30.5           NA
17           33.5           NA
18           29.5           20
19             NA           NA
20           31.0           15
> 
> sapply(Cars93_7, function(x) mean(x, na.rm=T))
Rear.seat.room   Luggage.room 
      29.10526       15.35294 
> 
> 
> Cars93_7 <- data.frame(sapply(Cars93_7, 
+                               function(x) ifelse(is.na(x), 
+                                                  mean(x, na.rm = TRUE), x)))
> 
> Cars93_7
   Rear.seat.room Luggage.room
1        26.50000     11.00000
2        30.00000     15.00000
3        28.00000     14.00000
4        31.00000     17.00000
5        27.00000     13.00000
6        28.00000     16.00000
7        30.50000     17.00000
8        30.50000     21.00000
9        26.50000     14.00000
10       35.00000     18.00000
11       31.00000     14.00000
12       25.00000     13.00000
13       26.00000     14.00000
14       25.00000     13.00000
15       28.50000     16.00000
16       30.50000     15.35294
17       33.50000     15.35294
18       29.50000     20.00000
19       29.10526     15.35294
20       31.00000     15.00000

 

 

 


(9) 그룹 별 평균값으로 결측값 대체하기 (filling missing values by group mean)


base 패키지를 사용할 수도 있긴 한데요, dplyr 패키지의 group_by 와 ifelse 조건문을 사용한  mutate 함수로 그룹 별 평균값으로 결측값 채우는 방법을 소개하겠습니다. 


> # make a sample DataFrame

> grp <- c(rep('a', 5), rep('b', 5))

> val <- c(1, 2, 3, NaN, 6, 2, 4, NaN, 10, 8)

> df <- data.frame(grp, val)

> df

   grp val

1    a   1

2    a   2

3    a   3

4    a NaN

5    a   6

6    b   2

7    b   4

8    b NaN

9    b  10

10   b   8



결측값을 제외하고 그룹 'a'와 그룹 'b' 별 평균을 계산해보겠습니다. 


> # mean value by group 'a' and 'b'

> library(dplyr)

> df %>% group_by(grp) %>% summarise(grp_mean = mean(val, na.rm = TRUE))

# A tibble: 2 x 2

  grp   grp_mean

  <fct>    <dbl>

1 a            3

2 b            6



그룹 'a'의 평균은 3, 그룹 'b'의 평균은 6이군요. 그러면 4번째 행에 있는 그룹 'a'의 'val' 칼럼 결측값을 그룹 'a'의 평균 3으로 대체(replace) 또는 채워넣기(fill in)를 해보겠습니다. 그리고 8번째 행에 있는 그룹 'b'의 'val' 칼럼 결측값을 그룹 'b'의 평균 6으로 대체해보겠습니다. 


> df %>% 

+   group_by(grp) %>% 

+   mutate(val = ifelse(is.na(val), mean(val, na.rm=TRUE), val))

# A tibble: 10 x 2

# Groups:   grp [2]

   grp     val

   <fct> <dbl>

 1 a         1

 2 a         2

 3 a         3

 4 a         3

 5 a         6

 6 b         2

 7 b         4

 8 b         6

 9 b        10

10 b         8 


 

--------------------

 

우에서 NA(Not Available)에 대해서 소개를 했는데요, 참고로 벡터에서 NaN (Not a Number), 무한값 Inf (Infinity), 유한값 finite 확인하는 방법도 마저 소개하겠습니다.

 

유의할 것은, is.na()에서 NA 뿐만 아니라 NaN 도 TRUE 를 반환합니다.

 

 

> my_data <- c(-1, 0, 10, NA, NaN, Inf)
>
> my_data
[1]  -1   0  10  NA NaN Inf
>
> is.finite(my_data)
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE
>
> is.na(my_data)
[1] FALSE FALSE FALSE  TRUE  TRUE FALSE
>
> is.nan(my_data)
[1] FALSE FALSE FALSE FALSE  TRUE FALSE
>
> is.infinite(my_data)
[1] FALSE FALSE FALSE FALSE FALSE  TRUE

 

 


많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 


Posted by R Friend R_Friend

댓글을 달아 주세요

  1. Anthony 2015.12.15 22:07  댓글주소  수정/삭제  댓글쓰기

    감사합니다~ 많은 도움이 됐습니다.^_^

  2. AshtrayK 2016.08.29 16:43 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 kjh입니다 ㅎㅎ
    오늘도 어김없이..
    is.na와 complete.cases 는
    TRUE FALSE를 리턴하는 함수라고 보면 되고
    na.omit은 결측값이 제거된 데이터 객체를 반환하는 것인가요?

  3. 셀리던트 2016.12.04 18:05  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 강좌 열심히 보며 따라 해보고 있습니다. 덕분에 도움이 많이 되네요 한가지 궁금한 점이 생겨서 전의 포스팅의 sapply / lapply를 활용하여 Cars93 변수별 결측값 구하기를 하려고 sapply(Cars93,sum(is.na))으로 실행해보니 오류가 나서 질문드립니다. Apply함수를 사용할때 이중 함수(sum(is.na)처럼) 사용은 불가능한가요?

    • R Friend R_Friend 2016.12.04 20:14 신고  댓글주소  수정/삭제

      is.na() 가 칼럼 단위가 아니라 원소(element) 단위로 TRUE 또는 FALSE 블리언값을 반환합니다.

      따라서 sapply(Cars93, sum(is.na))는 에러가 납니다.

      원하시는 분석을 하려면 colSums(is.na(Cars93)) 을 사용하시면 됩니다.

      참고로, sum(is.na(Cars93)) 와
      sum(sapply(Cars93, is.na)) 모두 전체 93개의 관측치 원소 중에서 결측값인 원소의 총 개수의 합인 13을 반환합니다.

  4. kusskt 2017.03.30 02:34  댓글주소  수정/삭제  댓글쓰기

    올려주시는 블로그 내용만 다 봐도 R은 웬만큼 다 할 수 있을 것 같습니다. R 연습하는 데 도움 굉장히 많이 되어서 감사하다는 말씀 전하구요, 열심히 공부하고 있습니다. ㅎㅎ

    위 내용 중에 데이터 프레임 결측값 대체에서 cars93[is.na(cars93)] <- 0 이라고 해도 다 대체가 되더군요. 너무 잘 설명해 주셨는데, 이 방식이 변수별 NA값 대체에 한정되어 있는 것처럼 보여서, 데이터 프레임 파트에 추가하셔도 좋을 것 같아서 글 남깁니다.
    항상 감사합니다!

  5. 꾸리꾸리 2019.05.09 21:04  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 추가로 연습하다 궁금한 부분이 생겨 질문 드립니다.

    이전의 transform으로 계산 ( 곱, 나누기, log값)을 진행 하였습니다

    계산 값이 무한대로 표시가 되면서 Inf and -Inf로 나타났을시 혹시 이부분을 문자 형태가 아닌 숫자 ( Inf 가 아닌 0.xxxxxx) 소수점으로 표시하는 방법이 있는지 궁금합니다.

    • R Friend R_Friend 2019.05.09 21:13 신고  댓글주소  수정/삭제

      지수표기를 숫자표기로 변경하는 방법은

      https://rfriend.tistory.com/224

      포스팅을 참고하시기 바랍니다.

    • 꾸리꾸리 2019.05.09 21:16  댓글주소  수정/삭제

      감사합니다 빠른 답변 정말 감사드립니다.

    • 꾸리꾸리 2019.05.09 21:35  댓글주소  수정/삭제

      안녕하세요 질문드립니다.

      포스팅 참고 후, 다시 수정을 진행중입니다.

      하지만, Inf & -Inf 를 처리하는 방법에 대해서는 아직 이해가 부족한것 같습니다.

      log값을 취하여 Inf & -Inf가 나온것을 숫자로 변환하는 쉬운 방법은 어떤 포스팅을 참고해야 할까요?

      계산후, 무한대 결측값을 이용하여 플롯을 작성해야 하는데 통계분석 진행에 큰 어려움이 생겨 질문드렸습니다.

    • R Friend R_Friend 2019.05.09 21:48 신고  댓글주소  수정/삭제

      원하시는 아웃풋을 제가 정확하게 이해한건지 좀 불확실한데요, 일단 제가 이해한데로 코드를 짜봤습니다. 아래 참고하시기 바랍니다.

      > # fixed notation
      > options(scipen=100)
      >
      > # infinite value
      > x <- Inf
      > is.infinite(x)
      [1] TRUE
      > x
      [1] Inf
      >
      > # converting infinite number into big integer
      > if (is.infinite(x) == TRUE) {
      + x = 100000
      + }
      > x
      [1] 100000


      ################
      그리고 참고로, 로그, 지수 관계에 대해서는 https://rfriend.tistory.com/295 포스팅을 참고하시기 바랍니다.

    • 2019.05.09 22:01  댓글주소  수정/삭제

      비밀댓글입니다

    • R Friend R_Friend 2019.05.09 22:12 신고  댓글주소  수정/삭제

      0에 대해서 log를 취하면 -Inf 가 나옵니다. 따라서 보통 log 연산할 때 매우 매우 작은 수를 더해서 -Inf 가 나오는 것을 방지하곤 합니다.

      > x <- 0
      > log2(x)
      [1] -Inf
      > log2(x+0.0000000001)
      [1] -33.21928

      ps. 저도 회사업무랑 공부랑 너무 바빠서 퇴근 후에도 시간을 쪼개서 쓰고 있습니다. 그래서 이렇게 계속 추가 답변을 드리기 힘이 듭니다. 이해해주세요. ㅠ_ㅠ

    • 꾸리꾸리 2019.05.09 22:15  댓글주소  수정/삭제

      답변 감사드립니다!

      서두없는 질문에도 친절히 답변해주셔서 감사합니다.

      그래도 어느정도 솔루션을 찾게되어 진행할 수 있게 되었습니다.

      감사합니다 !

    • R Friend R_Friend 2019.05.09 22:29 신고  댓글주소  수정/삭제

      어느정도 해결이 되었다니 정말 다행입니다.

  6. Tacong 2019.11.07 16:48  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 선생님 게시글 보면서 열심히 공부하고 있으며 많은 도움이 되고 있습니다 ㅜ 감사합니다

    궁금한게 있는데요

    8) 데이터프레임의 각 변수의 결측값을 각 변수 별 평균값으로 일괄 대체

    여기서 sapply(Cars93_7, function(x) mean(x, na.rm = T))

    function(x) mean(x) 이건 갑자기 왜 나오는 것인지 모르겠습니다 ㅜㅜ


    그리고 ifelse(is.na(x) 이 함수는 뭘 뜻하는건지 모르겠습니다 ㅜ

    • R Friend R_Friend 2019.11.07 17:21 신고  댓글주소  수정/삭제

      안녕하세요 Tacong님,

      supply(DayaFrame, Function) 은 데이터프레임 내 모든 변수들에게 함수 명령어를 동시에 적용하여 벡터 또는 행렬을 반환해주는 굉장히 유용한 함수입니다. (==> rfriend.tistory.com/33 참조)

      즉, 위의 예에서는 sapply(df, function)의 function 자리에 Cars93 데이터프레임의 모든 칼럼들의 결측값 여부를 확인(is.na(x))해서 TRUE이면 평균으로 결측값 대체하고, 결축측이 아니면 원래 값을 그대로 두라는 ifelse 조건절의 함수가 필요합니다. (==> ifelse 조건절은 rfriend.tistory.com/91 참조)

      두개 이상의 논리값 벡터에 대한 판단에 사용하는 ifelse(조건식, 표현식1, 표현식2) 형태는 "만약 조건식이 TRUE 이면 => Then 표현식1을 수행하고, FALSE이면 표현식2를 실행하라" 라는 의미입니다.

      사용자정의함수 (==> rfriend.tistory.com/96)를 따로 정의해놓고 sapply(df, functiin)의 function 자리에 넣을수도 있지민, 1줄짜리의 간단한 함수라면 이번 예제처럼 함수의 (사용자정의)이름없이 바로 function(x) 하고 바로 함수 표현식을 써줄 수도 있습니다. (마치 python의 lambda 와 유사함)

      이번 예제를 잘 익혀두시면 매우 유용한게요, 만약 apply 함수를 안쓴다고 하면 for loop 반복문을 써야 하니 코드도 길어지고 (큰 데이터셋의 경우) 속도도 느려집니다.

      도움이 되었기를 바랍니다.

이전 포스팅들 중에 tapply(), sapply()에 대해서 다른 함수를 설명하는 와중에 은근슬쩍 짧게 소개를 한적이 있습니다.

 

그런데 그때는 다른 함수를 설명하는 것이 주된 목적이다보니 tapply()만 따로 한두줄 소개하고 말고, 또는 sapply()만 따로 한두줄 설명하고 마는 식이었습니다. 

 

이번에는 복습도 할겸, 또 apply() 삼총사인 tapply(), sapply(), lapply() 가 각 각 뭐가 다르고, 무슨 특징이 있고, 어떤 때 쓰는 것인지에 대해서 비교해가면서 중점적으로 살펴보도록 하겠습니다.

 

 간략히 요약해서 비교하자면 아래와 같습니다.

 

함수 

 사용 목적

사용 형태

    결과 

 tapply()

요인(factor)의 수준(level)별로

특정 벡터에 함수 명령어를

동시에 적용

 tapply(벡터, 요인, 함수)

 벡터 또는 행렬

 sapply()

데이터 프레임 여러 변수에 함수

명령어 동시에 적용

 sapply(데이터 프레임, 함수)

 lapply(데이터 프레임, 함수)

 벡터 또는 행렬

 lapply()

 리스트

 

tapply()가 다른 두 함수와 다른 점은 tapply()는 요인(factor) 변수를 기준으로 해서 그룹별로 나누어서 통계 분석을 하고자 할 때 유용하게 쓸 수 있는 함수입니다.  아래 예시를 보면 좀더 직관적으로 이해할 수 있을 겁니다.

 

sapply()와 lapply()는 사용 목적이나 사용 형태는 동일합니다만, 차이점이 있다면 결과가 sapply()는 벡터 또는 행렬로 나오는 반면에, lapply()는 결과가 리스트로 나온다는 점입니다. 하나씩 예를 들어 설명해보도록 하겠습니다.

 

 

(1) tapply() : 요인의 수준별로 특정 벡터에 함수 명령어를 동시에 적용

 

MASS 패키지에 내장되어 있는 Cars93 데이터를 가지고 차량 유형(Type)별 고속도록 연비(MPG.highway)의 평균과 표준편차를 tapply()를 활용해 구해보겠습니다.  차량 유형별(Type)은 Compact, Large, Midsize, Small, Sporty, Van 의 6개의 수준(Level)로 구성된 요인(factor)입니다.

 

 

> library(MASS) > str(Cars93) 'data.frame': 93 obs. of 27 variables: $ Manufacturer : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ... $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ... $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ... $ Min.Price : num 12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ... $ Price : num 15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ... $ Max.Price : num 18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ... $ MPG.city : int 25 18 20 19 22 22 19 16 19 16 ... $ MPG.highway : int 31 25 26 26 30 31 28 25 27 25 ... $ AirBags : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ... $ DriveTrain : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ... $ Cylinders : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ... $ EngineSize : num 1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ... $ Horsepower : int 140 200 172 172 208 110 170 180 170 200 ... $ RPM : int 6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ... $ Rev.per.mile : int 2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ... $ Man.trans.avail : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ... $ Fuel.tank.capacity: num 13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ... $ Passengers : int 5 5 5 6 4 6 6 6 5 6 ... $ Length : int 177 195 180 193 186 189 200 216 198 206 ... $ Wheelbase : int 102 115 102 106 109 105 111 116 108 114 ... $ Width : int 68 71 67 70 69 69 74 78 73 73 ... $ Turn.circle : int 37 38 37 37 39 41 42 45 41 43 ... $ Rear.seat.room : num 26.5 30 28 31 27 28 30.5 30.5 26.5 35 ... $ Luggage.room : int 11 15 14 17 13 16 17 21 14 18 ... $ Weight : int 2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ... $ Origin : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ... $ Make : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ... >
> # 차량 Type별 고속도로 연비 평균
>
with(Cars93, tapply(MPG.highway, Type, mean)) Compact Large Midsize Small Sporty Van 29.87500 26.72727 26.72727 35.47619 28.78571 21.88889 >
> 차량 Type별 고속도로 연비 표준편차

> with(Cars93, tapply(MPG.highway, Type, sd))
 Compact    Large  Midsize    Small   Sporty      Van 
2.941088 1.272078 2.510584 5.609091 3.641187 1.452966 

 

만약 tapply()를 활용하지 않는다면

 - Cars93을 Type별로 MPG.highway를 쪼개서 (split()함수 또는 subset() 함수를 활용해서)

 - 각 수준(level)별로 쪼개진 벡터에다가 개별적으로 평균, 표준편차 함수를 일일이 적용한 후에 ...(만약 수준이 100개면 100면 반복작업)

 - 각 결과치를 indexing 해와서 cbind()혹은 rbind()로 묶어서 결과를 취합

하는 쌩 노가다 작업을 진행해야 합니다. tapply()가 손발의 고생을 덜어주는 유용한 함수라는 것을 알 수 있을 것입니다.

 

 

(2) sapply() : 데이터 프레임 여러 변수에 함수 명령어 동시 적용 
    (결과는 벡터 또는 행렬)

 

sapply()함수를 활용하여 Cars93의 27개 변수 각각의 속성(class)를 알아보도록 하겠습니다.

 

> sapply(Cars93, class)
      Manufacturer              Model               Type          Min.Price              Price          Max.Price 
          "factor"           "factor"           "factor"          "numeric"          "numeric"          "numeric" 
          MPG.city        MPG.highway            AirBags         DriveTrain          Cylinders         EngineSize 
         "integer"          "integer"           "factor"           "factor"           "factor"          "numeric" 
        Horsepower                RPM       Rev.per.mile    Man.trans.avail Fuel.tank.capacity         Passengers 
         "integer"          "integer"          "integer"           "factor"          "numeric"          "integer" 
            Length          Wheelbase              Width        Turn.circle     Rear.seat.room       Luggage.room 
         "integer"          "integer"          "integer"          "integer"          "numeric"          "integer" 
            Weight             Origin               Make 
         "integer"           "factor"           "factor"

 

 

만약 sapply()함수를 사용하지 않는다면,

 - class(Cars93$Manufacturer); class(Cars93$Model); class(Cars93$Type);   ...(중략).... ; class(Cars93$Make)

처럼 변수의 갯수만큼 (여기서는 27번) 쌩 노가다 작업을 해야합니다.

sapply()는 한줄이면 될 것을 말입니다.

 

 

(3) lapply() : 데이터 프레임 여러 변수에 함수 명령어 동시 적용
    (결과는 리스트)
 

 

 이번에는 lapply()함수로 Cars93 내 27개 변수의 속성(class)을 알아보도록 하겠습니다.  명령문 순서는 sapply()와 lapply()가 동일합니다만, 결과가 나오는 형태가 서로 다름을 확인할 수 있습니다. lapply()는 아래처럼 list 형태로 결과가 나옵니다. 필요한 부분 indexing 하기에 편리하겠지요.

 

> lapply(Cars93, class)
$Manufacturer
[1] "factor"

$Model
[1] "factor"

$Type
[1] "factor"

$Min.Price
[1] "numeric"

$Price
[1] "numeric"

$Max.Price
[1] "numeric"

$MPG.city
[1] "integer"

$MPG.highway
[1] "integer"

$AirBags
[1] "factor"

$DriveTrain
[1] "factor"

$Cylinders
[1] "factor"

$EngineSize
[1] "numeric"

$Horsepower
[1] "integer"

$RPM
[1] "integer"

$Rev.per.mile
[1] "integer"

$Man.trans.avail
[1] "factor"

$Fuel.tank.capacity
[1] "numeric"

$Passengers
[1] "integer"

$Length
[1] "integer"

$Wheelbase
[1] "integer"

$Width
[1] "integer"

$Turn.circle
[1] "integer"

$Rear.seat.room
[1] "numeric"

$Luggage.room
[1] "integer"

$Weight
[1] "integer"

$Origin
[1] "factor"

$Make
[1] "factor"

 

 

이상으로 apply() 삼총사, 명령문 단순 동일 반복을 한방에 해결할 수 있는, 그래서 손발의 수고를 덜어주는 tapply(), sapply(), lapply()에 대해서 알아보았습니다.  

 

도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 윤수한 2020.01.14 11:21  댓글주소  수정/삭제  댓글쓰기

    tapply 를 사용하는데, 하나의 분류를 더 추가하고 싶으면 어떻게 해야하나요?
    예를 들어서
    "origin 별 차량 Type별 고속도로 연비 평균" 을 구하고 싶으면요.

R 데이터 구조(structure), 유형(class) 에 대해서 예전에 포스팅을 했던 적이 있습니다.

 

R 데이터 구조(structure)에는 스칼라(scala), 벡터(vector), 요인(factor), 행렬(matrix), 데이터프레임(dataframe), 리스트(list) 가 있다고 소개를 해드렸습니다. (☞ R 데이터 구조 포스팅 보러가기)

 

그리고 R 데이터 유형(class)에는 숫자형 (numeric), 문자형 (character), 요인형 (factor) 등이 있다고 말씀드렸습니다. 오늘 포스팅에서는 R 데이터 객체의 유형을 (1) 확인, (2) 전환 하는 함수에 대해서 알아보도록 하겠습니다.

 

숫자형, 문자형에 대해서는 쉽게 알 수 있으므로, 요인형(factor)이 뭐고 왜 필요한 지에, 어디에 써 먹는지에 대해서 먼저 간략히 소개를 드린 후에 객체 유형 확인, 전환 함수로 넘어가도록 하겠습니다.  왜 공부해야 하는지도 모르면서 진도를 빼면 다 배우고 나서 'so what?' 하겠지요?  (물론, 나중에 통계분석, 그래프 쪽 가면 그때 가서 '아, 이래서 이거 배웠었구나...'하고 한번 더 복습하시면 되겠습니다.)

 

 

요인(factor)은 무엇이고, 어디에 써먹나? 

 

일단, 요인(factor)은 "~별 숫자형 벡터의 평균, ~별 숫자형 벡터의 합계" 등과 같이 요약 통계량을 집단이나 특성에 따라서 분석할 때 사용합니다. OLAP으로 치자면 '차원(dimension)' 역할을 하는 것이 되겠습니다.

 

MASS 패키지에 내장된 Cars93 데이터 프레임 내 변수들을 가지고 예를 들어보겠습니다.  

자동차 Type(Levels: Compact, Large, MidsizeSmall, Sporty, Van) 요인별로 고속도로 연비(MPG.highway)의 평균(mean)을 계산해보도록 하겠습니다.

 

> library(MASS) > str(Cars93) # 데이터 구조 데이터 프레임, 관측치 93개, 변수 27개 'data.frame': 93 obs. of 27 variables: $ Manufacturer : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ... $ Model : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ... $ Type : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ... $ Min.Price : num 12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ... $ Price : num 15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ... $ Max.Price : num 18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ... $ MPG.city : int 25 18 20 19 22 22 19 16 19 16 ... $ MPG.highway : int 31 25 26 26 30 31 28 25 27 25 ... $ AirBags : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ... $ DriveTrain : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ... $ Cylinders : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ... $ EngineSize : num 1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ... $ Horsepower : int 140 200 172 172 208 110 170 180 170 200 ... $ RPM : int 6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ... $ Rev.per.mile : int 2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ... $ Man.trans.avail : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ... $ Fuel.tank.capacity: num 13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ... $ Passengers : int 5 5 5 6 4 6 6 6 5 6 ... $ Length : int 177 195 180 193 186 189 200 216 198 206 ... $ Wheelbase : int 102 115 102 106 109 105 111 116 108 114 ... $ Width : int 68 71 67 70 69 69 74 78 73 73 ... $ Turn.circle : int 37 38 37 37 39 41 42 45 41 43 ... $ Rear.seat.room : num 26.5 30 28 31 27 28 30.5 30.5 26.5 35 ... $ Luggage.room : int 11 15 14 17 13 16 17 21 14 18 ... $ Weight : int 2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ... $ Origin : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ... $ Make : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...




 
> head(Cars93) # 상위 6개 관측치
  Manufacturer   Model    Type Min.Price Price Max.Price MPG.city MPG.highway            AirBags DriveTrain
1        Acura Integra   Small      12.9  15.9      18.8       25          31               None      Front
2        Acura  Legend Midsize      29.2  33.9      38.7       18          25 Driver & Passenger      Front
3         Audi      90 Compact      25.9  29.1      32.3       20          26        Driver only      Front
4         Audi     100 Midsize      30.8  37.7      44.6       19          26 Driver & Passenger      Front
5          BMW    535i Midsize      23.7  30.0      36.2       22          30        Driver only       Rear
6        Buick Century Midsize      14.2  15.7      17.3       22          31        Driver only      Front
  Cylinders EngineSize Horsepower  RPM Rev.per.mile Man.trans.avail Fuel.tank.capacity Passengers Length Wheelbase
1         4        1.8        140 6300         2890             Yes               13.2          5    177       102
2         6        3.2        200 5500         2335             Yes               18.0          5    195       115
3         6        2.8        172 5500         2280             Yes               16.9          5    180       102
4         6        2.8        172 5500         2535             Yes               21.1          6    193       106
5         4        3.5        208 5700         2545             Yes               21.1          4    186       109
6         4        2.2        110 5200         2565              No               16.4          6    189       105
  Width Turn.circle Rear.seat.room Luggage.room Weight  Origin          Make
1    68          37           26.5           11   2705 non-USA Acura Integra
2    71          38           30.0           15   3560 non-USA  Acura Legend
3    67          37           28.0           14   3375 non-USA       Audi 90
4    70          37           31.0           17   3405 non-USA      Audi 100
5    69          39           27.0           13   3640 non-USA      BMW 535i
6    69          41           28.0           16   2880     USA Buick Century
>

> > # 함수 명령문 형식: with(dataset, tapply(numeric vector, factor, function)) > with(Cars93, tapply(MPG.highway, Type, mean)) Compact Large Midsize Small Sporty Van 29.87500 26.72727 26.72727 35.47619 28.78571 21.88889

 

 

 

그래프를 그릴 때도 요인(factor)별로 구분해서 그린 후에 비교를 해보면 인사이트를 뽑아내는데 유용하게 사용할 수 있습니다. ggplot2 패키지를 이용해서 자동차 Type(Levels: Compact, Large, MidsizeSmall, Sporty, Van) 요인별로 고속도로 연비(MPG.highway)의 히스토그램을 그려보도록 하겠습니다.

 

> install.packages("ggplot2") # ggplot2 패키지 설치

> library(ggplot2) # ggplot2 패키지 호출

> # 명령식 형식: qplot(numeric vector, data=dataset, facets=factor~. , binwidth=n) > qplot(MPG.highway, data=Cars93, facets = Type~. , binwidth=2)

 

 

 

 

 그래프의 오른쪽에 보면 6개의 자동차 Type별로 구분이 되어서 히스토그램이 그려졌음을 알 수 있습니다.

 

위의 2개의 예에서 보는 것처럼 요인(factor)이 통계분석, 그래프 분석에 유용하게 쓰이므로, 반드시 요인(factor)으로 데이터 객체를 관리해야 하는것이 생깁니다. '요인'이 들어와 있어야 할 자리에 '숫자형'이나 '문자형'이 들어와 있으면 '요인형'을 넣어서 써야 하는 함수 명령문이 안 먹겠지요.  반면에, 문자(character)나 숫자(numeric)로 인식을 해야 하는게 있는데 R에 요인(factor)로 잘못 인식이 된 경우도 있을 수 있으며, 이런 경우 에러가 나는 주요 범인이 되곤 합니다. 

 

그래서 데이터셋을 받으면 제일 처음 하는 일이 str(), head()로 데이터셋의 구조, 변수, 관측치 등을 살피고, 그 다음으로 class()로 데이터 객체별 속성을 확인해보는 것이 반드시 필요합니다. 그리고 분석가가 원하는 속성으로 데이터가 안들어가 있다면 속성을 분석 목적에 맞게 전환해줄 필요가 생깁니다.

 

 

 

데이터 속성 확인 및 속성 전환 

 

 데이터 속성 확인 및 속성 전환에 대해 알아보기 위해 아래의 텍스트 파일을 불러와서 예를 들어보겠습니다.

 

(실습 파일 아래 다운로드 ☞ ) 

cust_profile_2.txt

 

불러오고 나면 이렇게 생긴 데이터 프레임이 되겠습니다

 

데이터셋의 성격/분석 목적 상 'cust_id'는 character, 'last_name'도 character, 'age'는 numeric, 'gender'는 factor 로 입력되어야 합니다.

그런데 데이터셋이 실제로 어떻게 입력되었는지 2개의 case로 나누어서 확인해보도록 하겠습니다.

 

 

 (1) stringsAsFactor = FALSE   로 한 경우 (요인으로 인식하지 말라는 옵션)

 

> # 외부 텍스트 파일 불러오기 (위에 파일 링크 다운로드 해서 사용 가능)

> cust_profile_1 <- read.table("C:/Users/user/Documents/R/cust_profile_2.txt", + header = TRUE, + sep = ",", + stringsAsFactor = FALSE, + na.strings = "") >

> is.character(cust_profile_1$cust_id) # 문자형 여부 속성 확인 [1] FALSE

> is.character(cust_profile_1$last_name) [1] TRUE > is.numeric(cust_profile_1$age) # 숫자형 여부 속성 확인 [1] FALSE > is.factor(cust_profile_1$gender) # 요인형 여부 속성 확인 [1] FALSE > > sapply(cust_profile_1, class) # 한꺼번에 cust_profile_1 데이터셋에 class() 함수를 적용하라는 명령문 cust_id last_name age gender "integer" "character" "character" "character"

 

 

'last_name'만 character로 원하는 속성으로 들어가 있고, 나머지는 R이 저의 맘을 몰라주고 R 마음대로 데이터 속성이 들어가 있습니다.

'gender'는 남/녀 성별 통계분석이나 그래프 그릴 때 요인(factor)으로 요긴하게 써먹어야 하므로 문자형(character)를 요인형(factor)로

as.factor() 함수를 써서 전해보도록 하겠습니다.

 

> cust_profile_1$gender <- as.factor(cust_profile_1$gender)
> is.factor(cust_profile_1$gender)
[1] TRUE



요인형의 값으로서, 미리 정해진(predefined), 한정된 수의 범주형 값(finite number of categorical values)을 수준(levels) 이라고 합니다. 요인 데이터의 수준을 확인하거나 지정, 변경하려면 levels() 함수를 사용합니다.  


> # level check

> levels(cust_profile_1$gender)

[1] " F" " M"

> # change levels' name from {"F", "M"} to {"FEMALE", "MALE"}

> levels(cust_profile_1$gender) <- c("FEMALE", "MALE")

> levels(cust_profile_1$gender)

[1] "FEMALE" "MALE"  



혹은 factor() 함수labels 옵션을 사용해서 아래처럼 할 수도 있습니다. 



> cust_profile_1$gender <- factor(cust_profile_1$gender, labels = c("FEMALE", "MALE")

 




다음으로, stringsAsFactor = TRUE 로 했을 때는 어떻게 데이터를 불어오는지 알아보겠습니다.

 

 

 (2) stringsAsFactor = TRUE   로 한 경우 (요인으로 인식하라는 옵션)

 

> cust_profile_2 <- read.table("C:/Users/user/Documents/R/cust_profile_2.txt", 
+                              header = TRUE, 
+                              sep = ",", 
+                              stringsAsFactor = TRUE, 
+                              na.strings = "")
> 
> sapply(cust_profile_2, class)
  cust_id last_name       age    gender 
"integer"  "factor"  "factor"  "factor" 
> 
> 
> cust_profile_2$cust_id <- as.character(cust_profile_2$cust_id)
> cust_profile_2$age <- as.numeric(cust_profile_2$age)
> sapply(cust_profile_2, class)
    cust_id   last_name         age      gender 
"character"    "factor"   "numeric"    "factor"

 

 

'gender'는 요인(factor)로 제대로 불러왔는데요, 명목형 변수인 'cust_id'는 문자형(character)가 아닌 숫자형(interger)로, 'last_name'을 문자형(character)가 아닌 요인형(factor)로, 'age'를 숫자형(numeric)이 아닌 요인형(factor)로 잘못 불러왔습니다.

 

자, 이제 아래 표 처럼 이터 특성/분석 목적에 맞게 데이터 속성을 전환 시켜 보도록 하겠습니다.

 

 변수명

 기존 속성

전환 후 속성

 속성 전환 함수

 cust_id

numeric

 character

 as.character()

 last_name

 factor

 character

 as.character()

 age

 factor

 numeric

 as.numeric()

 gender

 factor

 factor

 as.factor()

 

'gender'는 이미 요인형(factor)로 제대로 잘 들어가 있기 때문에 아래 예시에서는 별도로 속성 전환하지는 않았습니다.  

 

> cust_profile_2$cust_id <- as.character(cust_profile_2$cust_id)
> cust_profile_2$last_name <- as.character(cust_profile_2$last_name)
> cust_profile_2$age <- as.numeric(cust_profile_2$age)
>

> sapply(cust_profile_2, class) cust_id last_name age gender "character" "character" "numeric" "factor"

 

많은 도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 권호근 2016.02.29 22:16  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.

    올려주신 내용을 기반으로 하여 조금씩 R을 공부하고 있습니다.

    감사드립니다.

    다름이 아니라 두번째 예를 R에서 돌릴시 아래와 같은 에러 메시지가 나오기에 왜그런지 여쭙고자 합니다~

    > qplot(MPG.highway, data=Cars93, facets = Type~. , binwidth=2)
    다음에 오류가 있습니다list2env(members, envir = e) :
    names(x)는 반드시 x와 같은 길이를 가지는 문자형 벡터이어야 합니다

    그럼 답변 부탁드릴께요!

    • R Friend R_Friend 2016.03.05 12:07 신고  댓글주소  수정/삭제

      안녕하세요 권호근 님,
      댓글로 남겨주신 예제에 대해서 제가 다시 한번 실행해보니 저는 에러 없이 잘 실행이 됩니다. 프로그래밍상에 특별한 문제는 없습니다.

      저도 댓글에 남겨주신 에러가 왜 발생했는지 정확히는 알지 못하겠습니다. 다만 추측컨데, 이번 예제를 실행하시기 전에 R로 이전에 사용했던 데이터셋의 변수가 메모리에 상주해있다가 충돌이 난 것이 아닌가 의심이 됩니다.

      Rstudio를 사용하신다면 우측 상단의 'Environment' 의 붙 모양(전체 제거)의 아이콘을 클릭해서 메모리 상에 올라와있는 데이터프레임, 벡터, 행렬, 리스트, 함수 등을 전부 다 삭제를 하신 후에, 에러가 발생했던 아래의 예제를 다시한번 실행해보시기 바랍니다.

      library(MASS)
      install.packages("ggplot2")
      library(ggplot2)
      qplot(MPG.highway, data=Cars93, facets = Type~. , binwidth=2)

  2. AshtrayK 2016.09.06 16:02 신고  댓글주소  수정/삭제  댓글쓰기

    값의 종류가 7개 이하면 팩터화하는 코드를 만드려고 하는데요
    mtcars2 <- mtcars
    for(i in 1:length(mtcars2) )
    {if(length(table(mtcars2[i])) <=7){mtcars2[i] <- as.factor(mtcars2[i])
    print(names(mtcars2[i]))
    }}

  3. AshtrayK 2016.09.06 16:04 신고  댓글주소  수정/삭제  댓글쓰기

    모바일로 써서 좀 오타가 있을지도 모르지만... 일단 코드에서 에러가 나는 부분은 as.factor에 데이터 프레임을 집어넣어서 그런 것 같은데요..
    이런경우 데이터 프레임에서 칼럼에 대해서 순회하도록 인덱싱을 하려면 어떻게 해야할까요?..
    위처럼 데이터 프레임이 아니라 벡터로 리턴해주는 걸루요

    • R Friend R_Friend 2016.09.06 16:45 신고  댓글주소  수정/삭제

      sapply 함수 활용해보세요.
      http://rfriend.tistory.com/m/33

      퇴근 후에 R script 볼께요.

    • AshtrayK 2016.09.06 16:47 신고  댓글주소  수정/삭제

      안그래도 해보고 있었는데ㅎㅎ 감사합니다. 일단 결과는 lapply를 써야 제대로 나오구요. sapply를 쓰면 돌아가기는 하는데 변수들 구조가 좀 이상하게 변합니다.

    • AshtrayK 2016.09.06 16:50 신고  댓글주소  수정/삭제

      강좌를 한가지 요청하자면..
      sapply를 돌려서 나온 결과물, 이상한 구조로 나오는 변수들을 볼 수 있는데요. 이게 뭔지 어떤의미인지 알고 싶습니다. 여기나오는 - attr(*, "dimnames") = List of 2 ~~중략~~
      이 부분이요.. S3인지뭔지 그부분인가 싶기도하구요 ㅠㅠ

    • R Friend R_Friend 2016.09.06 16:51 신고  댓글주소  수정/삭제

      sapply는 벡터나 행렬, lapply는 리스트를 반환해요. data.frame()로 반환결과를 한번 싸주면 될거 같아요

    • AshtrayK 2016.09.06 16:53 신고  댓글주소  수정/삭제

      data.frame으로 싸니까 잘 나오네요! 이런 방법도 있군요.
      lapply랑 결과가 똑같이 잘나오네요.

  4. 꿀두히 2016.09.08 15:16  댓글주소  수정/삭제  댓글쓰기

    안녕하세요?

    혼자서 R을 공부하고 있는데 올려주시는 강좌들이 정말 많은 도움이 됩니다. 감사합니다.

    한 가지 질문이 있는데요,

    파일을 불러올 때 'age'라는 변수가
    stringsAsFactor = FALSE 일 경우 'character'로
    stringsAsFactor = TRUE 일 경우 'factor'로 불러와 지는 것이 궁금합니다.

    cust_id 라는 변수 처럼 숫자로 이루어진 변수인데 왜 'integer' 로 되지 않는지 여쭙고 싶습니다.

    • R Friend R_Friend 2016.09.08 15:50 신고  댓글주소  수정/삭제

      반갑습니다, 꿀두희님.

      제가 만들었던 예시 데이터셋의 age에 NA값이 들어있어서 그렇습니다.

      만약 NA 대신에 정수값이 들어가있다면 질문하신대로 데이터 유형이 integer로 나올거예요

R의 장점이자 특징 중의 하나가 벡터 연산이 가능해서 프로그램을 깔끔하게 짤 수 있고 연산처리 속도가 빠르다는 점, Indexing을 활용하면 필요한 벡터 객체만 가져다가 다시 프로그램의 input으로 사용할 수 있다는 점 등에 대해 이전에 소개해준 적이 있는데요,

 

그럼 이번에는 벡터 끼리 혹은 벡터와 스칼라 간에 비교할 때 쓰는 비교/논리 연산자에 대해서 알아보도록 하겠습니다.

 

 

 

벡터의 비교/논리 연산자 

 

 

-- 비교 연산자 --

 

(1) ~ 보다 크다 :  >

 

aa, bb 라는 벡터와 cc 라는 스칼라를 만들어서 비교/논리 연산자를 실습해 보겠습니다.

 

> aa <- c(1, 2, 3, 4, 5, 6, 7) # 벡터
> bb <- c(7, 6, 5, 4, 3, 2, 1) # 벡터
> cc <- c(4) # 스칼라
> 
> aa > bb
[1] FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE
> aa > cc
[1] FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE

 

'>' 연산자를 실행하면 'TRUE', 'FALSE'와 같은 논리형 벡터가 아래에 출력됩니다.

 

'aa' 벡터는 구성요소가 7개인 반면에, 'cc' 스칼라는 구성요소가 1개 밖에 없는데도 비교 연산이 되었습니다. 이처럼 서로 구성요소의 개수가 다르면 스칼라가 R 내부적으로 7번 반복이 되어서 순환연산이 실행되었습니다.  벡터끼리 연산할 때도 서로 개수가 다르면 개수가 작은 쪽이 순환연산이 이루어져서 서로 짝을 맞추어서 연산이 실행됩니다.

 

 

(2) ~ 보다 크거나 같다 : >=

 

> aa >= bb
[1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE
> aa >= cc
[1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE

 

 

(3) ~ 보다 작다 : <

 

> aa < bb
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE
> aa < cc
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE 

 

 

(4) ~ 보다 작거나 같다 : <=

 

> aa <= bb
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
> aa <= cc
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
> aa =< cc
Error: unexpected '<' in "aa =<"

 

세번째 명령어에 보면 부호의 순서가 '=<' 처럼 '~보다 같거나 작다'의 순서로 했더니 에러가 났습니다. '<=' 처럼 '~보다 작거나 같다'의 순서로 부호 사용하기 바랍니다.

 

 

(5) 서로 같다 : ==

 

> aa == bb
[1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
> aa == cc
[1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

 

R에서 객체를 할당할 때 '<-' 또는 '=' 를 사용하는데요, 비교 연산자의 '==' 과 헷갈리지 않도록 합니다.

 

 

(6) 서로 같지 않다 : !=

 

> aa != bb
[1]  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
> aa != cc
[1]  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
> aa =! cc
> 

 

 

서로 같지 않다도 부호의 순서를 '!=' 로 해야지만 논리형 벡터가 결과 값으로 나옵니다. 세번째 줄처럼 '=!'로 하면 논리형 벡터 결과가 안나오니 순서 주의하세요.

 

 

 

-- 논리 연산자 --

 

(7) 동시에 조건 만족 (aa AND bb)  : aa & bb

 

> aa <- c(1, 2, 3, 4, 5, 6, 7) # 벡터 > bb <- c(7, 6, 5, 4, 3, 2, 1) # 벡터 > cc <- c(4) # 스칼라

>

> aa > 3 & aa < 6
[1] FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE
> aa >= 3 & aa <= 6
[1] FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE

 

 

 

 

(8) 또는 조건 만족 (aa OR bb) : aa | bb

 

> aa < 3 | aa > 6
[1]  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE
> aa <= 3 | aa >= 6
[1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE 

 

 

-- 논리 연산자에 대한 추가 연산 --

 

(9) 조건 만족하는 논리형 벡터에 대한 합계, 평균 : sum(), mean()

 

> sum(aa > 3 & aa < 6)
[1] 2
> sum(aa < 3 | aa > 6)
[1] 3
> mean(aa > 3 & aa < 6)
[1] 0.2857143
> mean(aa < 3 | aa > 6)
[1] 0.4285714

 

R은 'TRUE'는 '1'로, 'FALSE'는 '0'으로 내부적으로 인식합니다. 따라서 sum()이나 mean() 과 같은 숫자형 벡터에 대한 통계 함수를 사용할 수가 있습니다.

 

 

(10) 비교/논리 연산 결과에 대해 하나라도 (any) 혹은 전부(all) TRUE 가 있는지 여부 확인 : any(), all()

 

> any(aa > 3 & aa < 6)
[1] TRUE
> all(aa > 3 & aa < 6)
[1] FALSE
> any(aa > 8)
[1] FALSE
> any(aa == 1)
[1] TRUE
> all(aa == 1)
[1] FALSE

 

 

위의 (1) ~ (9)번까지의 예들이 1~7까지의 혹은 7~1까지의 몇 개 안되는 구성요소를 가지고 예를 들다보니 눈으로 보면 TRUE가 뭐고 몇 개인지 바로 알 수 있지만요, 실제 분석할 때는 많은 경우 관측치 수가 수 만개, 수 백만개, 수 천만개가 되는 경우가 많습니다. 그때 일일이 육안으로 TRUE, FALSE를 확인한다는 것은 사실상 불가능합니다. 따라서 비교/논리 연산 결과에 대해 sum(), mean(), any(), all() 등과 같은 함수를 적절히 활용해서 즉시 데이터 셋에 대한 상황 파악(탐색)을 하는 것이 필요합니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. gongju5908 2018.10.05 14:35  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 항상 도움이 되고 있습니다. 감사합니다.
    질문을 드리고 싶은데요^^
    1번 : temp1=temp[which(temp$HCHK_APOP_PMH_YN!="1" & temp$HCHK_HDISE_PMH_YN !="1"),]
    2번 : JJ1=JJ[which(!JJ$HCHK_APOP_PMH_YN %in% c("1") & !JJ$HCHK_HDISE_PMH_YN %in% c("1")),]

    숫자가 차이가 있어요. 혹시 1번 2번 차이점이 무엇인지 알고 계신가요????

R 은 숫자형 벡터를 처리할 수 있는 편리한 함수를 다수 제공합니다.

 

이번 포스팅에서는 절대값 계산, 제곱근 계산, x 보다 크거나 같은 정수(지붕), x 보다 작거나 같은 정수(바닥), 소수점 이하는 잘라서 버리거나 반올림 하기, 로그 변환, 지수 변환, 팩토리얼 등의 벡터 처림 함수에 대해서 알아보도록 하겠습니다. 

 

문자형 벡터 처리 함수는 나중에 별도로 기회를 봐서 포스팅하도록 하겠습니다.

 

 

R 숫자형 벡터 처리 함수 

 

 

1) 절대값 계산 : abs(x)

 

> abs(10)
[1] 10
> abs(-10)
[1] 10

 

 

2) 제곱근 계산 : sqrt(x)

 

> sqrt(16) [1] 4 > sqrt(-16) # 허수는 NaN [1] NaN Warning message: In sqrt(-16) : NaNs produced

 

 

3) x 보다 크거나 같은 정수 (지붕 정수) : ceiling(x)

 

> ceiling(5.88)
[1] 6
> ceiling(6.00)
[1] 6

 

 

4) x 보다 작거나 같은 정수 (바닥 정수) : floor(x)

 

> floor(5.88)
[1] 5
> floor(5.00)
[1] 5

 

 

5) x 소숫점 이하는 잘라서 버림 : trunc(x)

 

> trunc(5.88)
[1] 5
> trunc(5.10)
[1] 5

 

 

6) x 를 소수점 n자리로 반올림 : round(x, digits=n)

 

> round(5.88, digits = 1)
[1] 5.9
> round(5.88, digits = 0)
[1] 6
> round(5.10, digits = 0)
[1] 5

 

 

7) x 를 밑이 n인 log 취하기 : log(x, base=n)

 

> log(10, base=2) # 밑이 2인 로그 [1] 3.321928 >

> log(10, base = exp(1)) # 자연로그 [1] 2.302585 >

> log(10) # 자연로그, base = exp(1) 을 생략해도 동일함 [1] 2.302585 >

> log(10, base=10) # 밑이 10인 상용로그 [1] 1 >

> log10(10) # 상용로그의 다른 표현법 [1] 1 

 

 

8) x 를 지수변환하기 : exp(x)

 

> exp(log(10, base=exp(1)))
[1] 10
> exp(1)
[1] 2.718282
> exp(10)
[1] 22026.47 

 

 

9) x factorial : factorial(x)

 

> factorial(2)
[1] 2
> factorial(3)
[1] 6
> factorial(4)
[1] 24
> factorial(5)
[1] 120 

 

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 김진양 2019.01.01 16:02  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. r프렌드님! 데이터 분석 항상 재미있게 공부하고 있습니다.
    새해복 많이 받으세요^^

    다름 아니라, x를 지수로 변환하는 것의 의미가 잘 이해가 가지 않아서요.
    구글링, 네이버 검색을 했는데도 나오지가 않아서 질문드리게되었습니다.

    1을 지수로 변환했다는 뜻이 밑이 몇인 지수로 변환시키는건가요?

    제가 수학에 배움이 짧아 질문을 하면서도 정확한 용어를 지칭하기 어려워 댓글 달아주기 힘드실 것 같습니다. 죄송합니다!

R에서 데이터를 입력할 때 c() 를 사용하는데요, 일정한 반복이나 규칙을 따르는 데이터 입력이라면 함수 명령문을 이용하는게 단순 업무를 줄이는 방법이 되겠지요.

 

예전에 R의 장점이자 강점 중의 하나가 벡터 연산이 파워풀하다는 점이라고 했는데요,

 

아래의 rep(), seq() 함수를 곁들여서 벡터 연산에 활용하면 좋겠지요?!

 

 

 

 rep(), seq() 반복 데이터, 일정한 구조/순차 데이터 생성

 

 

(1) rep() : 일정한 데이터 반복

 

반복하고자 하는 4가지 형태별로 아래 예시를 들었습니다.

 


> ## "a"를 10번 반복

> rep("a", times = 10) [1] "a" "a" "a" "a" "a" "a" "a" "a" "a" "a" >

> ## "1"을 15번 반복

> rep(1, times = 15) [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # 1 은 숫자형 그대로 임 >

> ## ("a" & "1") 을 5번 반복

> rep( c("a", 1), 5) [1] "a" "1" "a" "1" "a" "1" "a" "1" "a" "1" # "1"이 숫자형이 아니라 문자형으로 변환됨 >

> ## "a"를 먼저 5번 반복하고, "1"을 10번 반복

> rep( c("a", 1), c(5,10))
 [1] "a" "a" "a" "a" "a" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1"

 

 




특정 범위의 정수 숫자를 각각 동일한 횟수로 반복하고자 한다면 rep(x:y, each=z) 함수를 사용하면 편합니다.

1~3까지의 정수를 10번씩 반복하는 예제는 아래와 같습니다.



> # repeat 1 at 10 times, 2 at 10 times, 3 at 10 times
> rep(1:3, each=10)
 [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3

 




1행부터 20행까지 있는 데이터 프레임에서 1부터 5까지의 반복하는 숫자를 행 기준으로 반복하는 예제는 아래와 같습니다. dataframe$var <- rep(c(1:5), len = ncol(dataframe)) 처럼 할당하는 방법도 있고, cbind()를 사용하는 방법도 있는데요, 2가지 방법 모두 아래에 소개하였습니다. 진동, 주파수 처럼 일정한 주기성을 띠는 데이터를 분석하는 경우 분석 단위/구간 지정을 위해서 은근히 많이 사용하는 데이터 전처리 방법입니다.



> ## rep() exmaple
> x <- c(1:20)
> y <- rep(1, times = 20)
> z <- rep(c(1, 2), c(10, 10))
> xyz <- data.frame(cbind(x, y, z))
> xyz
    x y z
1   1 1 1
2   2 1 1
3   3 1 1
4   4 1 1
5   5 1 1
6   6 1 1
7   7 1 1
8   8 1 1
9   9 1 1
10 10 1 1
11 11 1 2
12 12 1 2
13 13 1 2
14 14 1 2
15 15 1 2
16 16 1 2
17 17 1 2
18 18 1 2
19 19 1 2
20 20 1 2
> 
> ## repeating c(a:b) from first row until last row : way 1
> xyz$seq_no_1 <- rep(c(1:5), len = nrow(xyz))
> xyz
    x y z seq_no_1
1   1 1 1        1
2   2 1 1        2
3   3 1 1        3
4   4 1 1        4
5   5 1 1        5
6   6 1 1        1
7   7 1 1        2
8   8 1 1        3
9   9 1 1        4
10 10 1 1        5
11 11 1 2        1
12 12 1 2        2
13 13 1 2        3
14 14 1 2        4
15 15 1 2        5
16 16 1 2        1
17 17 1 2        2
18 18 1 2        3
19 19 1 2        4
20 20 1 2        5
> 
> ## repeating c(a:b) from first row until last row : way 2
> seq_no_2 <- rep(c(1:5), len = nrow(xyz))
> xyz <- cbind(xyz, seq_no_2)
> xyz
    x y z seq_no_1 seq_no_2
1   1 1 1        1        1
2   2 1 1        2        2
3   3 1 1        3        3
4   4 1 1        4        4
5   5 1 1        5        5
6   6 1 1        1        1
7   7 1 1        2        2
8   8 1 1        3        3
9   9 1 1        4        4
10 10 1 1        5        5
11 11 1 2        1        1
12 12 1 2        2        2
13 13 1 2        3        3
14 14 1 2        4        4
15 15 1 2        5        5
16 16 1 2        1        1
17 17 1 2        2        2
18 18 1 2        3        3
19 19 1 2        4        4
20 20 1 2        5        5

 

 



(2) seq() : 일정한 구조/순차 데이터 생성

 

 아래 예시를 참고하세요.

 

 

> ## c()를 이용한 1부터 10까지 입력 (1 단위씩 커짐)
> c(1:10)
 [1]  1  2  3  4  5  6  7  8  9 10
> 
> ## seq()를 이용한 1부터 10까지 입력 (1 단위씩 커짐)
> seq(from=1, to=10)
 [1]  1  2  3  4  5  6  7  8  9 10
> 
> ## seq_len()을 이용한 1부터 10까지 입력 (1 단위씩 커짐)
> seq_len(10)
 [1]  1  2  3  4  5  6  7  8  9 10
> 
> ## seq()를 이용한 1~10까지의 숫자를 2단위씩 증가시키면서 입력
> seq( from = 1, to = 10, by=2 )  # from, to 는 제외해도 괜찮음
[1] 1 3 5 7 9
> 
> ## seq()를 이용한 1~10까지의 수를 5개의 숫자로 등간격으로 구성 (숫자는 5개, 구간은 4개)
> seq( 1, 10, length = 5 )  # from, to 는 제외해도 괜찮음
[1]  1.00  3.25  5.50  7.75 10.00
> 
# length.out : 개수 지정
> seq(from = 1, by = 2, length.out = 10)
 [1]  1  3  5  7  9 11 13 15 17 19

 

 

 

 

많은 도움이 되었기를 바랍니다. 

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

엑셀에서 외부 텍스트 데이터를 불러올 때 (1) ',', ':', ' ' 등의 구분자로 column을 구분하는 방식이 있고, (2) 일정한 간격, 고정된 폭으로 되어 있어서 대화상자에서 구분하는 폭/선을 마우스로 지정해주는 방식이 있습니다.

 

R에서 (1) 구분자가 들어있는 외부 텍스트 파일 불러오기는 지난번 포스팅에서 read.table(), read.csv() 함수를 소개했는데요,

 

이번 포스팅에서는 (2) 고정된 폭의 외부 텍스트 파일을 불러오는 함수 read.fwf() 함수에 대해 알아보겠습니다.

 

 

read.fwf() : 일정한 간격, 고정된 폭 구조의 외부데이터 불러오기

 

fwf 는 fixed width file 의 약자입니다.

아래 예제를 보면서 따라해보면 금방 사용법 알 수 있을 거예요.

 

 아래 형태처럼 1~2자리, 3~5자리, 6~8자리로 일정한 간격으로 되어있는 데이터를 불어와보도록 하겠습니다.

data_fwf.txt

 

 

read.fwf("디렉토리 경로",

       widths = c(간격 설정),

        col.names = c(변수명 설정))

 

> # fwf() 함수로 일정한 간격 외부 텍스트 데이터 불러오기

> data_fwf <- read.fwf("C:/Users/user/Documents/R/data_fwf.txt", # 경로 '/' 주의 + widths = c(2,3,3), + col.names = c("Var_1", "Var_2", "Var_3"))

 

 

불러온 데이터를 확인해 보면, col.names에서 할당한 변수명대로 해서 일정한 간격으로 구분되어 데이터가 들어가 있음을 알 수 있습니다.

 

> data_fwf
  Var_1 Var_2 Var_3
1    11   aaa   222
2    33   bbb   444
3    55   ccc   666

 

구분자를 안쓰는 경우 자칫 중간에 데이터가 꼬일 수 있으므로 read.fwf() 함수는 조심해서 써야 합니다. 기계에서 일정한 간격으로 흐트러짐 없이 쏟아져나오는 센서 데이터와 같이 일정한 간격, 고정된 구조의 데이터라고 확신이 있을 때만 사용하시기 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. ruser 2017.04.15 21:19  댓글주소  수정/삭제  댓글쓰기

    항상 잘 보고 있습니다. 파이썬도 추가 되었군요, 함께 하겠습니다. ^^
    근데 제가 fwf 관련해서 궁금한 것이 있는데요,
    오라클 같은 DB에서는 컬럼의 길이를 설정할 때, 바이트로 따지는 것 같더라구요.
    이 때문에 해당 컬럼에 더블-바이트 문자인 한글을 입력하면 글자 하나당 2자리를 차지하게 되는데요, 여기서 이런 식으로 DB에서 생성된 데이터파일과(txt 등) 고정길이에 대한 메타데이터를 가지고 R의 read.fwf 등을 활용할 경우 컬럼 구분이 꼬일 수가 있더라구요.

    예를 들면 DB에서 길이 할당을 10으로 한 varchar2 컬럼에 한글로 "바나나"가 입력된 경우에,
    DB는 "바나나"를 6자리로 읽고 나머지를 스페이스 4자리로 해서 10자리를 채우는데,
    R에서는 "바나나"를 3자리로 읽고 나머지를 스페이스 4자리를 더해서 총 7자리로 읽어버리는 것이죠.

    혹시 이런 문제에 대한 해결 방법을 아시나요?

    LaF, readr 등 생소한 R 패키지들을 찾아보고 있는데, 좀처럼 답을 찾기가 어렵네요,,

    • R Friend R_Friend 2017.04.17 22:43 신고  댓글주소  수정/삭제

      안녕하세요 ruser님, R이랑 Python 사용하신다니 반갑니다.

      저는 질문해주신 것과 같은 문제를 겪어보지는 못해서요, 정확히는 모르겠는데요, 그냥 떠오르는 아이디어를 적어보자면요,

      R로 dbConnet 해서 할게 아니라 그냥 DB에서 text 파일로 exporting을 먼저 한 후에, R에서 read.fwf() 함수로 외부파일 불러오기를 하면 어떨까 싶은데요.

      DB에서 text 파일로 exporting할 때 white space 없어지게 해놓고, 한번 정돈된 text 파일을 R이 읽어오면 될거 같은데요. 제가 문제를 잘 이해하고 답변을 쓴건지 장담 못하겠네요. ^^;

  2. 박정욱 2017.05.08 15:56  댓글주소  수정/삭제  댓글쓰기

    항상 잘보고 공부하고 있습니다. 궁금한 점이 있어 문의 드립니다.

    매일 매일 하기와 같은 파일 이름으로 저장이 되는데

    T_Speed1_20170505, T_Speed1_20170506, .......

    T_Torque1_20170505, T_Torque1_20170506, ......

    이런한 파일이 10개정도 됩니다.이것을 파일 합치기를 적용하고 합니다.

    X <- c(20170505)

    data3 <- read.csv("Y:/SMART FACTORY/4. R data/file/T_Speed1_20170505.csv",header = TRUE, stringsAsFactors = F)

    data4 <- read.csv("Y:/SMART FACTORY/4. R data/file/T_Torque1_20170505.csv",header = TRUE, stringsAsFactors = F)

    T_Speed1_X,T_Torque1_X로 X라는 변수로 지정 가능 할까요??

    이게 안되면 어떤한 방법이 있을까요??





    • R Friend R_Friend 2017.05.08 17:01 신고  댓글주소  수정/삭제

      안녕하세요 박정욱님,

      아래에 포스팅해놓은 글 참고하시기 바랍니다.

      (1) '폴더 내 여러개 파일들을 Loop 돌려서 자동으로 불러와서 하나의 객체로 합치기'
      => http://rfriend.tistory.com/219

      (2) assign 함수를 사용해서 객체의 이름에 다른 이름 추가하기
      => http://rfriend.tistory.com/108

 

R 데이터 프레임 구조는 분석에 가장 많이 사용하는 구조의 데이터 셋입니다.

 

데이터 프레임의 변수를 활용하는 방법 3가지를 알아보도록 하겠습니다.

 

 

 

 R 데이터 프레임 활성화: with(), attach() & detach()

 

(1) 데이터 프레임 이름$변수명 : 입력할 거리가 몇 개 안되는 경우 적합

 

> ## 데이터 프레임 이름을 지정하지 않으면 R은 어디에서 변수를 가져와야 할지 알지 못함. 에러 발생

> mean(mpg) Error in mean(mpg) : object 'mpg' not found
>
> ## '데이터 프레임 이름$변수명' 형식으로 '$'를 이용해 객체 지정해주어야 R이 이해를 함
> mean(mtcars$mpg) [1] 20.09062 

>
> ## '0-1' 변환 해보기

> tf_0_1 <- (max(mtcars$mpg) - mtcars$mpg) / (max(mtcars$mpg) - min(mtcars$mpg)) > tf_0_1 [1] 0.54893617 0.54893617 0.47234043 0.53191489 0.64680851 0.67234043 0.83404255 0.40425532 [9] 0.47234043 0.62553191 0.68510638 0.74468085 0.70638298 0.79574468 1.00000000 1.00000000 [17] 0.81702128 0.06382979 0.14893617 0.00000000 0.52765957 0.78297872 0.79574468 0.87659574 [25] 0.62553191 0.28085106 0.33617021 0.14893617 0.77021277 0.60425532 0.80425532 0.53191489

* mtcars 는 R base 패키지에 내장된 데이터 프레임으로서, 자동차 32개 브랜드에 대해 11변수 값 조사해 놓은 자료.

str(mtcars) 해보면 데이터 구조, 변수명, 변수 개수, 관찰치 개수, 변수별 상위 데이터셋 미리보기 가능.

 

 

(2) with(데이터 프레임 이름, R 명령문) : R 명령문이 한 줄인 경우에만 사용 가능

 

위의 '$'는 R 명령어를 입력할 때마다 매번 '데이터 프레임 이름$변수명' 처럼 데이터 프레임 이름을 입력해주어야 하므로, 입력해야할 R 명령문이 많아지만 꽤 번거로운 방법이라고 하겠습니다. 모르면 손.발이 고생한다는 말이 딱 맞겠지요.

 

이럴 경우 손.발의 고생을 덜어주기에 유용한 함수가 with() 입니다. 위의 '0-1 변환'을 with()함수를 써서 해보겠습니다.

 

> tf_0_1_2 <- with(mtcars, (max(mpg) - mpg) / (max(mpg) - min(mpg)) )
> tf_0_1_2
 [1] 0.54893617 0.54893617 0.47234043 0.53191489 0.64680851 0.67234043 0.83404255 0.40425532
 [9] 0.47234043 0.62553191 0.68510638 0.74468085 0.70638298 0.79574468 1.00000000 1.00000000
[17] 0.81702128 0.06382979 0.14893617 0.00000000 0.52765957 0.78297872 0.79574468 0.87659574
[25] 0.62553191 0.28085106 0.33617021 0.14893617 0.77021277 0.60425532 0.80425532 0.53191489 

 

(1)번의 '$'를 사용한 노가다보다는 with()함수를 쓰니 훨씬 간결해지고 손도 덜 가지요?

다만, with()함수는 한 줄의 명령어만 가능하다보니 동일 데이터 프레임에 대해서 다수의 R 명령어를 써야 하는 상황이라면 attach(), detach() 함수를 사용해야 합니다.

 

 

(3) 활성화 시작 attach(데이터 프레임 이름), 끝 detach(데이터 프레임 이름): 다수의 R 명령문 입력 시 적합

 

공통적으로 계속 사용되는 대상 데이터 프레임을 지정(활성화 한다고 함)할 때는 attach(), 다른 데이터 프레임으로 바꾸고자 기존의 지정(활성화)된 데이터 프레임을 해제하고자 할때는 detach() 함수를 사용합니다.

 

> ## 데이터 프레임 활성화 attach()

> attach(mtcars) > tf_0_1_3 <- (max(mpg) - mpg) / (max(mpg) - min(mpg)) > tf_0_1_3 [1] 0.54893617 0.54893617 0.47234043 0.53191489 0.64680851 0.67234043 0.83404255 0.40425532 [9] 0.47234043 0.62553191 0.68510638 0.74468085 0.70638298 0.79574468 1.00000000 1.00000000 [17] 0.81702128 0.06382979 0.14893617 0.00000000 0.52765957 0.78297872 0.79574468 0.87659574 [25] 0.62553191 0.28085106 0.33617021 0.14893617 0.77021277 0.60425532 0.80425532 0.53191489 > > hist(mpg)

 

> summary(mpg) Min. 1st Qu. Median Mean 3rd Qu. Max. 10.40 15.42 19.20 20.09 22.80 33.90 >

> ## 데이터 프레임 활성화 해제 : detach() > detach(mtcars)

 

attach(데이터 프레임 이름 mtcars) 이후에 변수 변환도 하고, 히스토그램 그래프도 그리고, summary() 함수로 연비(mpg: mileage per gasolin)의 요약통계량을(최소값, Q1, 중앙값, 평균, Q3, 최대값) 구하는데 있어서 공통적으로 'mtcars' 데이터 프레임을 대상으로 해서 변수를 가져다가 R 함수가 실행되었습니다.

 

만약 '$'문을 사용했더라면 매번 데이터 프레임 이름을 계속 명기해주어야 하므로 노가다 좀 했겠지요? ^^

 

attach() 함수를 사용할 때 주의할 점이 있는데요, 그건 다음번 포스팅에서 다루도록 하겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요

  1. HJE 2018.03.24 17:21  댓글주소  수정/삭제  댓글쓰기

    블로그를 통해 R을 쉽게 배우고 있습니다. ^^

    이 포스트의 마지막 부분에서

    'summary() 함수로 실린더(cyl: cylinder)별로 연비(mpg: mileage per gasolin)도 구하..'
    이렇게 나와있는데요.

    mpg의 평균, 중앙값, quartiles를 구하신 거 맞지요?
    실린더별로 연비를 구한다는 것은 어떤 뜻인지 풀어서 설명해주실 수 있을까요? ^^

    • R Friend R_Friend 2018.03.24 17:51 신고  댓글주소  수정/삭제

      안녕하세요 HJE님,
      댓글에 질문 남겨주신 부분을 보니 제가 포스팅을 잘못 썼던 거였네요. ^^; 제가 잘못썼던 포스팅 본문을 수정했습니다. 혼선을 드려서 죄송합니다. 그리고 제가 수정할 수 있도록 댓글 남겨주셔서 감사합니다.

      연속형 변수에 대한 그룹별(요인별) 요약통계량을 구할 수 있는 R 패키지와 함수는 아래의 포스팅을 참고하시면 됩니다. 다시 한번 댓글 감사드립니다.

      => http://rfriend.tistory.com/125

아래의 '태풍 찬홈 실제 이동 경로, 미 예보가 정확... 한국이 가장 빗나가' 신문 기사와 관련하여, 생각나는 것들 몇가지 적어봅니다. 

 

제대로 조사하고 정리하자면 일이 되어 차일피일 미루다가 글을 못쓰게 되곤 하야, 이슈가 될 때 짧고 굷게 휘갈겨 적어봅니다.

 

 

 

 

 

* 신문기사 출처: http://news.chosun.com/site/data/html_dir/2015/07/13/2015071300191.html

 2015.07.13, 박은호 기자, 조선일보

 

 


 

 

 

(1) 기상 예측, 쉽지만은 않다.

 

카오스(Chaos) 이론에 대해서 공부해보신 분들은 아마 접해보셨을 이야기입니다. 기상학자 로렌츠가 간단한 기상모델을 만들어서 컴퓨터로 계산을 해보았다고 합니다. 그러다가 다음날 다시 계산을 해보았는데 그 전날 계산한 값과 매우 큰 차이를 보이길레 모델에 뭐가 잘못되었나 한참을 찾았다고 합니다. 찾다, 찾다 결국 찾아낸 것이 초기 입력값의 소숫점 3째자리(?) 이하의 아주 작은 값을 입력하지 않았더니 예측값이 시간이 지날수록 확연히 달라지더라는 것을 발견하고 깜짝 놀랐다는 이야기 입니다.  

 

 

(위 그림 출처: http://ws.ajou.ac.kr/~nldc/int_xaos.htm   , 아마도 카오스 책 아니면 복잡계 책에서 봤던 그림인데, 책을 다 중고로 팔아버려서 확인할 수는 없고....기억이 잘 안나네요 ^^;)

 

소위 나비효과라고도 많이들 얘기하는데요, 베이징에서 나비가 날개짓하면 뉴욕에서 해일이 발생할 수도 있다는 이야기의 원조가 바로 로렌츠 기상모델 곡선이 되겠습니다.

 

증폭 효과로 인해서 초기의 아주 극히 작은 값에도 민감하게 시간이 지날수록 오차가 커지게 되고, 장기적인 예측은 오차가 벌어질 수밖에 없다는 내용이 되겠습니다. 일기예보, 특히 장기 일기예보는 원래 이렇게 어려운 겁니다. 한국 기상청 너무 욕하지 마시길요. ^^'

 

 

(2) 기상 예측, 그동안 많이 발전하여 왔다.

 

그렇다 하더라도 요즘 아침 출근할 때마다 스마트폰으로, TV뉴스로 일기예보 확인하고 우산 챙기고 다니시죠?  피부로 느끼기에 유용하다고 느끼지 않으시는지요?  지역을 격자로 나누어서 각 지역거점마다 기상 관련 정보를 수집하고, 이를 과거 데이터와 날씨 정보를 활용하여 모델을 만들어서 점점 더 날씨 예측을 정교화해나가고 있습니다.  요즘은 '국지성 기상정보'를 수집/예측해서 돈 주고 파는 사업자까지 나왔습니다. '국지성(기존 보다 더욱 작은 cell로 나누어서) 기상예보'가 가능하고 또 돈이 되니깐 하는 사업이겠지요.

 

 

(3) 기상 예측, 앞으로 더 발전할 여지가 있다.

 

앞으로 날씨 정보를 수집하는 cell을 더욱 작게 하고, 또 3차원 공간으로 축을 하나 더 세워서 cubic 개념으로 정보를 수집하고 예측 모델링을 한다면 (가령, 가정을 해보자면 말입니다. ^^;), 즉, 데이터를 더 촘촘히 수집해서 장기간 축적하고 모델링을 한다면 기상 예측의 정확도도 더욱 높아지겠지요.

 

 

(4) 빅데이터 기술이 큰 역할을 할 것이다.

 

다만, (3)번 처럼 하게 되면 문제가 되는 것이, 데이터 저장을 위한 공간도 기하급수적으로 늘어나고(물론, 저장 비용이 매우 싸지고 있으므로 이건 좀 작은 문제라고 치고...), 더불어서 연산을 위한 IT 비용과 시간도 기하급수적으로 늘어난다는 것이 문제가 되겠습니다.

 

하지만, 요즘 빅데이터의 분산병렬처리 기술을 활용하면 비용효율적으로, 또 매우 빨른 시간 안에 이러한 연산을 처리할 수 있는 환경이 열렸습니다. 예전에는 불가능했던게 이제는 가능하게 된 것이지요.  기상청에서도 아마도 이미 슈퍼컴퓨터를 쓰고 있을 텐데요, 앞으로 더욱 싼 비용으로 더욱 파워풀한 슈퍼컴퓨터를 쓸 수 있게 될겁니다.

 

 

(5) 확률로 제시했더라면 좋았을 것을... 단기예측은 정확하지만 장기로 갈수록 불확실...

 

한국 기상청에서 이번에 미.일.중 기상청 대비 찬홈 태풍 이동경로 예측 정확도가 꼴찌이다 보니 여론의 뭇매를 맞는 것이겠지요. 한국 기상청에서 태풍 찬홈의 이동 경로를 몇 개의 경로를 제시하고 각 예상 경로별로 확률로 제시를 했더라면 더 좋았을 텐데 싶습니다. (1)번 로렌츠 기상모델 곡선에서 얘기했지만, 단기 예측 대비 장기 예측으로 갈 수록 불확실성이 증폭(!)되다 보니 하나님이 아닌 이상은 정확하게 예측하는게 매우 매우 매우 힘들다는 것을 인정하는게 맞다고 보며, 결국 확률로 제시할 수밖에 없고, 시간이 지나면서 계속 새로 들어온 정보를 가지고 예측을 update해주는 수밖에 없다고 생각합니다.

 

그래야, 최악의 시나리오의 가능성에 대해서도 사람들이 생각해볼 여지를 줄 수 있고, 또 대비할 수 있는 생각을 해볼 여지도 줄 수 있는거 아닐까요?  기상청에서 한국 말고 중국쪽으로 멀리 돌아서 태풍이 지나간다고 단정적으로(? 다수 시청자가 이리 받아들이지 않을까요?) 말하는 거랑, 미국이 제시한 코스로도 올 확률이 xx%이다 라고 같이 제시해 주는 거랑은 아마 시청자들이 받아들이는 감이 다를거라고 생각합니다.

 

 

(6) 한.미.일.중 비교를 하려면, 샘플 사이즈를 키워서 비교해달라.

 

마지막으로, 기자분께 한마디 하자면, 한국 기상청이 태풍 찬홈 이동경로 예측에서 미.일.중 대비 꼴찌였다고 했는데요, 한국 기상청의 기상예보 실력을 미.일.중과 비교를 해서 소위 '가설 검증(hypothesis test)'을 해보려면 이번 태풍 찬홈 예보 case 하나만 가지고 한국 기상청을 까기에는 한국 기상청이 좀 억울해 할 것 같습니다. 통계학에서는 보통 샘플 사이즈가 30개 넘으면 중심극한의 정리에 의해 t분포->정규분포로 근사하고, 신뢰수준 xx%, 유의수준 xx% 에서 통계적으로 가설이 유의미한지 아닌지를 검증하지요.

 

기자님께서 최근에 국내에 영향을 끼쳤던 태풍 30개에 대해서 한.미.일.중 기상청 별로 이동 경로를 어떻게 예보를 했었는지, 한.미.일.중 기상청 예보별로 정확도 등수를 매겼을 때 한국 기상청이 다른 나라 기상청보다 우수하다고 할 수 있는지, 평균 수준인지, 아니면 띨띨하다고 할 수 있는지에 대해서 검증을 해보았더라면 한국 기상청이 덜 억울했을 것 같습니다.

 

태풍 찬홈 뒤에 또 다른 태풍이 오고 있다고 하던데요, 한국 기상청이 다음번에는 태풍 경로 예보 잘 맞추시길, 그리고 아무쪼록 농사짖는 분들께 피해가 덜 갔으면 하는 바램으로 이번 포스팅 마칩니다.

 

Posted by R Friend R_Friend

댓글을 달아 주세요

 

R 데이터 객체를 신규로 생성했거나, 외부에서 불러왔거나, 아니면 R 패키지에 내장되어 있는 데이터 셋을 활용한다고 했을 때 데이터 객체의 현황, 특성에 대해서 파악하는 것이 필요합니다.

 

이에 유용한 함수들을 알아보도록 하겠습니다.

 

 

 R 데이터 객체 탐색을 위한 함수 

 

R에 기본으로 내장되어 있는 'mtcars' 라는 데이터 프레임을 가지고 아래의 각 함수들의 예시를 들어보겠습니다.

'mtcars' 데이터는 1974 Motor Trend US magazine에서 자동차 디자인과 성능에 관해 추출한 11개의 변수로 구성된 데이터 프레임입니다.

 

> mtcars
                     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2 

 

'mrcars' 라고 콘솔창에 치면 위의 박스 상자에 있는 것처럼 데이터 보기가 가능합니다. 이처럼 데이터 관찰치와 변수가 몇 개 안되면 콘솔창이나 아니면 environment 창에서 데이터셋을 눌러서 미리보기를 할 수 있겠읍니다만, 데이터 관찰치가 몇 백만이 되고 변수도 수천개가 넘는 데이터 객체라면 무리겠지요. 그래서 아래 함수들이 필요합니다.

 

 

(1) str(객체) : 데이터 구조, 변수 개수, 변수 명, 관찰치 개수, 관찰치의 미리보기

 

> str(mtcars)
'data.frame':	32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ... 

 

mtcars 가 '32개의 관측치', '11개의 변수'로 되어있는 '데이터 프레임'이고, 각 변수명과 변수들의 유형, 그리고 상위 10개의 관측치가 미리보기 형식으로 제시됩니다. 데이터 셋 탐색을 위해 제일 처음 해보면 좋을 유용한 함수입니다.

 

 

(2) head(), tail() : 상위 6개, 하위 6개 관측치 미리보기

 

> head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1


>
tail(mtcars) mpg cyl disp hp drat wt qsec vs am gear carb Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2 Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.5 0 1 5 4 Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.5 0 1 5 6 Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.6 0 1 5 8 Volvo 142E 21.4 4 121.0 109 4.11 2.780 18.6 1 1 4 2 

 

관측치가 수백만, 수천만 건인 경우는 상위 혹은 하위 몇개만 미리보기를 할 수 있으면 유용하겠지요.

 

 

(3) dim() : 데이터 객체의 차원

 

> dim(mtcars)
[1] 32 11 

 

str() 함수로 파악이 전부 가능한 정보인데요, 데이터 객체의 차원만 알고 싶거나 아니면 데이터 객체의 차원을 벡터로 해서 indexing해서 쓸일이 있을 때 이 함수를 사용하면 되겠지요.

 

 

(4) length() : 데이터 객체의 요소들의 개수

 

> length(mtcars) [1] 11

 

> length(mtcars$mpg)
[1] 32

 

첫번째 length(mtcars) 는 mtcars 데이터셋의 변수들의 개수를,

두번째 lenght(mtcars$mpg)는 mtcars의 데이터셋의 mpg라는 변수의 관측치의 개수를 나타냅니다.

(length()를 벡터에 사용하면 관측치의 개수를 나타냄)

목적에 맞게 골라서 사용하면 되겠습니다.

 

 

(5) names() : 데이터 객체 구성요소 이름

 

> names(mtcars)
 [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear" "carb"

 

데이터 객체의 변수명을 알고 싶고, indexing해서 사용하고 싶으면 names() 함수를 사용하면 되겠습니다.

 

 

(6) class() : 데이터 객체 구성요소의 속성

 

> class(mtcars)
[1] "data.frame"
> sapply(mtcars, class)
      mpg       cyl      disp        hp      drat        wt      qsec        vs        am 
"numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" 
     gear      carb 
"numeric" "numeric" 

 

첫번째의 class(mtcars)는 데이터 객체가 '데이터 프레임'임을 나타내고 있으며,

두번째 sapply(mtcars, class)는 'mtcars'라는 데이터 프레임의 모든 변수에다가 'class()라는 함수를 적용해라(sapply)고 했을 때의 결과로서, 11개의 각 변수별로 속성을 나타내고 있습니다.

(참고로, sapply()는 동일한 함수를 모두 적용하라는 함수입니다. 여기서는 class()라는 함수를 mtcars 내의 모든 변수에 공통으로 적용하라는 뜻입니다)

이 또한 목적에 맞게 골라서 사용하면 되겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend

댓글을 달아 주세요