개발 프로젝트에 가보면 서버와 클라이언트 간 데이터 전송할 때 XML 보다 기능은 적지만 보다 간단하고 파싱도 빠른 JSON(JavaScript Object Notation, (/ˈsən/ JAY-sən)) 데이터 포맷을 많이 사용합니다. 


JSON 은 JavaScript Object Notation 이라는 이름처럼 JavaScript 에서 유래하기는 했습니다만, 프로그래밍 언어에 독립적으로 기능하는 데이터 포맷입니다. 


JSON 이 무엇인가를 이해하는데 있어, 어떤 사람을 기술하는데 JSON 표기를 사용한 아래의 예제를 참고하시면 도움이 될 듯 합니다. 



[ 사람을 기술하는데 사용한 JSON 표기 예시 (JSON representation describing a person) ]


{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ],
  "children": [],
  "spouse": null
}

 * source : https://en.wikipedia.org/wiki/JSON




개발하는 분이라면 위 예제의 JSON 데이터 포맷이 아주 익숙할 것입니다만, 개발 경험이 없는 분석가의 경우 매우 생소하게 느낄 수 있습니다.  R에서 사용하는 데이터 구조로 스칼라, 벡터, 행렬, 배열, 데이터프레임, 리스트 등이 있는데요, 특히 행렬, 데이터프레임의 경우 2차원의 행(row)과 열(column)로 데이터셋이 구성이 되어 있습니다. JSON과는 많이 다르기 때문에 처음 JSON을 본 분석가는 아마 '이거 뭐지?' 하고 당황할 것 같습니다 (제가 그랬어요. ^^;).


그나마 위의 SJON 데이터는 들여쓰기(indentation)이 이쁘게 되어 있어서 가독성이 좋은 것이구요, 개발자들이 건네주는 SJON 파일을 보면 아래처럼 들여쓰기 없이 옆으로 죽~ 이어져 있어서 가독성이 매우 떨어지다 보니 더 당황하게 되는거 같습니다. @@~


{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null} 



그래서 보통은 JSON parser 의 도움을 받아서 들여쓰기(indentation)을 해서 데이터 구조, 위계체계를 살펴보곤 합니다. 

아래는 http://www.jsonparseronline.com/ 이라는 사이트에 들어가서 JSON 데이터(왼쪽)를 parsing (오른쪽) 해본 것입니다. 


[ JSON parser (http://www.jsonparseronline.com) ]





위의 데이터를 자세히 보시면 "address""phoneNumbers"는 다른 항목과는 달리 하위에 nested data 들을 가지고 있습니다. 일종의 데이터 위계체계가 있는 셈인데요, 아래 예시처럼 어떤 사람에 대한 데이터를 JSON 데이터 포맷으로 구조화하는 방식이 합리적이고 효율적으로 보입니다. 다만, R이나 Python (numpy, pandas 모듈), SAS, SPSS 등의 분석 툴을 사용하는 분석가라면 생소할 수 있다는점을 빼면 말이지요. 





서론이 길었습니다. 


이번 포스팅에는 R의 jsonlite 패키지를 사용해서 


(1) JSON 포맷의 데이터를 R DataFrame 으로 변환

     (converting from JSON to R DataFrame)


(2) R DataFrame 을 JSON 포맷의 데이터로 변환

     (converting from R DataFrame to JSON)


하는 방법에 대해서 알아보겠습니다. 


jsonlite 패키지를 사용하면 JSON 데이터 포맷을 구조를 R이 알아서 잘 이해를 해서 R 분석가가 익숙한 R DataFrame으로 자동으로 바꾸어 주며, 그 반대로 R DataFrame을 JSON 데이터 포맷으로도 바꾸어주니 매우 편리한 패키지입니다. 



먼저, jsonlite 패키지와 (웹에서 JSON 데이터를 호출할 때 사용하는) httr 패키지를 설치하고 불러와 보겠습니다. 



install.packages("jsonlite")

library(jsonlite)


install.packages("httr")

library(httr)




 (1) JSON 포맷의 데이터를 R DataFrame 으로 변환 (converting from JSON to R DataFrame)

      : fromJSON() 함수


아래는 jsonlite 패키지의 fromJSON() 함수를 사용해서 "https://api.github.com/users/hadley/repos" 의 github API 로부터 JSON 데이터를 요청(request)해서 R DataFrame으로 변환해 본 예제입니다. 



[ (R로 불러오기 전의) JSON 원본 데이터 포맷 (https://api.github.com/users/hadley/repos) ]





자세히 보면 "owner" 는 nested data 로 login, id 등의 데이터를 가지고 있습니다. 






30개의 관측치와 69개의 변수를 가지고 있는 DataFrame으로 잘 변환이 되었음을 알 수 있습니다. 


[ R jsonlite package를 사용해서 JSON 데이터를 R DataFrame 으로 변환한 모습 ]



> # (1) converting JSON to R DataFrame

> df_repos <- fromJSON("https://api.github.com/users/hadley/repos")

> str(df_repos)

'data.frame': 30 obs. of  69 variables:

 $ id               : int  40423928 40544418 14984909 12241750 5154874 9324319 20228011 82348 888200 3116998 ...

 $ name             : chr  "15-state-of-the-union" "15-student-papers" "500lines" "adv-r" ...

 $ full_name        : chr  "hadley/15-state-of-the-union" "hadley/15-student-papers" "hadley/500lines" "hadley/adv-r" ...

 $ owner            :'data.frame': 30 obs. of  17 variables:

  ..$ login              : chr  "hadley" "hadley" "hadley" "hadley" ...

  ..$ id                 : int  4196 4196 4196 4196 4196 4196 4196 4196 4196 4196 ...

  ..$ avatar_url         : chr  "https://avatars0.githubusercontent.com/u/4196?v=3" "https://avatars0.githubusercontent.com/u/4196?v=3" "https://avatars0.githubusercontent.com/u/4196?v=3" "https://avatars0.githubusercontent.com/u/4196?v=3" ...

  ..$ gravatar_id        : chr  "" "" "" "" ...

  ..$ url                : chr  "https://api.github.com/users/hadley" "https://api.github.com/users/hadley" "https://api.github.com/users/hadley" "https://api.github.com/users/hadley" ...

  ..$ html_url           : chr  "https://github.com/hadley" "https://github.com/hadley" "https://github.com/hadley" "https://github.com/hadley" ...

  ..$ followers_url      : chr  "https://api.github.com/users/hadley/followers" "https://api.github.com/users/hadley/followers" "https://api.github.com/users/hadley/followers" "https://api.github.com/users/hadley/followers" ...

  ..$ following_url      : chr  "https://api.github.com/users/hadley/following{/other_user}" "https://api.github.com/users/hadley/following{/other_user}" "https://api.github.com/users/hadley/following{/other_user}" "https://api.github.com/users/hadley/following{/other_user}" ... 

 ...이하 생략 ...


> names(df_repos)

 [1] "id"                "name"              "full_name"         "owner"             "private"          

 [6] "html_url"          "description"       "fork"              "url"               "forks_url"        

[11] "keys_url"          "collaborators_url" "teams_url"         "hooks_url"         "issue_events_url" 

[16] "events_url"        "assignees_url"     "branches_url"      "tags_url"          "blobs_url"        

[21] "git_tags_url"      "git_refs_url"      "trees_url"         "statuses_url"      "languages_url"    

[26] "stargazers_url"    "contributors_url"  "subscribers_url"   "subscription_url"  "commits_url"      

[31] "git_commits_url"   "comments_url"      "issue_comment_url" "contents_url"      "compare_url"      

[36] "merges_url"        "archive_url"       "downloads_url"     "issues_url"        "pulls_url"        

[41] "milestones_url"    "notifications_url" "labels_url"        "releases_url"      "deployments_url"  

[46] "created_at"        "updated_at"        "pushed_at"         "git_url"           "ssh_url"          

[51] "clone_url"         "svn_url"           "homepage"          "size"              "stargazers_count" 

[56] "watchers_count"    "language"          "has_issues"        "has_projects"      "has_downloads"    

[61] "has_wiki"          "has_pages"         "forks_count"       "mirror_url"        "open_issues_count"

[66] "forks"             "open_issues"       "watchers"          "default_branch"   





nested data 를 가진 "owner" 에 딸린 데이터 항목으로 login, id 등 총 17개 데이터 항목이 있군요. 



> # nested DataFrame in owner

> names(df_repos$owner)

 [1] "login"               "id"                  "avatar_url"          "gravatar_id"        

 [5] "url"                 "html_url"            "followers_url"       "following_url"      

 [9] "gists_url"           "starred_url"         "subscriptions_url"   "organizations_url"  

[13] "repos_url"           "events_url"          "received_events_url" "type"               

[17] "site_admin"

 




nested data 를 가진 "owner" 변수에 대해 하위 변수까지 위계 구조를 반영해서 데이터를 indexing 해오는 방법은 아래를 참고하세요. 4가지 방법 모두 동일한 결과를 반환합니다. 



> # different indexing, the same results

> df_repos[1:3,]$owner$login

[1] "hadley" "hadley" "hadley"

> df_repos[1:3,"owner"]$login

[1] "hadley" "hadley" "hadley"

> df_repos$owner[1:3,"login"]

[1] "hadley" "hadley" "hadley"

> df_repos$owner[1:3,]$login

[1] "hadley" "hadley" "hadley"

 




 (2) R DataFrame 을 JSON 포맷의 데이터로 변환 (converting from R DataFrame to JSON)

       : toJSON() 함수


위에서 JSON 포맷 데이터를 웹에서 호출해서 "df_repos"라는 이름의 R DataFrame으로 변환을 했었는데요, 이번에는 jsonlite패키지의 toJSON() 함수를 사용해서 거꾸로 R DataFrame 을 원래의 JSON 데이터 포맷으로 변환해보겠습니다. 



> # (2) converting R DataFrame to JSON

> json_repos <- toJSON(df_repos)

 




R DataFrame 데이터를 JSON 데이터 포맷으로 변환한 결과를 cat() 함수, prettify() 함수, minify() 함수를 사용해서 차례대로 살펴보겠습니다. 


cat() 함수, minify() 함수는 R의 head() 함수와 기능이 비슷합니다. 

 


> cat(json_repos)

[{"id":40423928,"name":"15-state-of-the-union","full_name":"hadley/15-state-of-the-union","owner":{"login":"hadley","id":4196,"avatar_url":"https://avatars0.githubusercontent.com/u/4196?v=3","gravatar_id":"","url":"https://api.github.com/users/hadley","html_url":"https://github.com/hadley","followers_url":"https://api.github.com/users/hadley/followers","following_url":"https://api.github.com/users/hadley/following{/other_user}","gists_url":"https://api.github.com/users/hadley/gists{/gist_id}","starred_url":"https://api.github.com/users/hadley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hadley/subscriptions","organizations_url":"https://api.github.com/users/hadley/orgs","repos_url":"https://api.github.com/users/hadley/repos","events_url":"https://api.github.com/users/hadley/events{/privacy}","received_events_url":"https://api.github.com/users/hadley/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/hadley/15-s... <truncated>

 




# not including indentation, whitespace

minify(json_repos)

 






prettify() 함수는 들여쓰기, 공백을 사용해서 JSON 데이터를 파싱해서 표기해줌으로써 아래에 화면캡쳐해 놓은 것처럼 가독성이 매우 좋습니다.  nested data 를 가진 "owner" 데이터에 대해서 아래처럼 정확하게 원래의 JSON 데이터포맷으로 변환을 해놨습니다. jsonlite 패키지 참 똑똑하지요? ^^



# including indentation, whitespace

prettify(json_repos, indent = 4)




JSON 데이터 포맷 관련해서 serializeJSON, stream_in, stream_out 등 추가적인 기능이 필요한 분은 아래 [Reference]의 jsonlite package 매뉴얼을 참고하시기 바랍니다. 


[Reference] https://cran.r-project.org/web/packages/jsonlite/jsonlite.pdf


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


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



저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

이번 포스팅에서는 외부의 엑셀(Excel) 파일로 존재하는 데이터를 RStudio 를 사용해서 R로 불러오기 하는 방법을 소개하겠습니다. 


저는 주로 DB에 직접 connect해서 데이터를 내리거나, 아니면 csv 나 txt 형태의 데이터를 R로 불러와서 분석에 사용하곤 합니다만(read.table() 함수 참고), 엑셀이 워낙 숫자 다루는데 강력하고 편리하다 보니 엑셀 데이터를 가져다가 R에서 사용해야 하는 경우도 있을 것입니다. 그리고 RStudio 에서 GUI로 Excel, SAS, SPSS, Stata 포맷을 데이터를 불러올 수 있는 기능이 있으니 클릭 몇 번으로 해결할 수 있으니 편리합니다. 


예제로 사용할 'cust_profile.xlsx'라는 이름의 엑셀 자료는 아래처럼 생겼습니다. 

(주의사항 1) 여러개의 sheet 중에서 'cust_profile' 라는 이름의 첫번재 sheet에 있는 데이터를 불러오고 싶습니다. 

(주의사항 2) 1~2번째 행(row)는 사용하지 않을 것이구요, 3번째 행부터 데이터를 불러오고 싶습니다.  1번째 열은 사용하지 않고 2번째 열부터 데이터를 불러오고 싶습니다. (정확히는 B3:E8 range)

(주의사항 3) 'gender' 열의 세번째 관측치 값이 결측값(missing value) 인데요, R로 불러왔을 때 결측값( 'NA')으로 잘 인식해서 불러오게 하고 싶습니다.  


[ 엑셀 예제 파일 ] 

* 실습 예제 엑셀 파일 첨부 ☞   cust_profile.xlsx



우선 R에서 "readex" package를 설치하고 로딩해보겠습니다. 


(1) {readxl} package 설치 및 로딩하기 



# installing and loading readxl package 

install.packages('readxl')

library(readxl)

 



엑셀 데이터를 R로 불러오는 방법에는 (a) R script를 이용하는 방법과,  (b) RStudio GUI 를 이용하는 방법의 2가지가 있습니다.  차례대로 소개하겠습니다. 


  (2) R script를 사용해서 엑셀 데이터 불러오기


엑셀 파일이 들어있는 경로(path), 사용할 sheet 이름, 불러올 데이터의 범위(range), 칼럼 이름(column name), 칼럼 유형(column type), cell 에 값이 비어있는 결측값에 사용할 문자열(비어 있으면 디폴트로 결측값으로 인식함)을 입력해주면 됩니다. 

> # importing excel file by using read_excel() function

> cust_profile <- read_excel("C:/Users/Administrator/Documents/cust_profile.xlsx", # path

+                            sheet = "cust_profile", # sheet name to read from

+                            range = "B3:E8", # cell range to read from

+                            col_names = TRUE, # TRUE to use the first row as column names

+                            col_types = "guess", # guess the types of columns

+                            na = "NA") # Character vector of strings to use for missing values

> str(cust_profile)

Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 5 obs. of  4 variables:

 $ ID    : chr  "c1" "c2" "c3" "c4" ...

 $ gender: chr  "F" "M" NA "M" ...

 $ age   : num  30 28 46 65 38

 $ name  : chr  "Kim" "Lee" "Park" "Moon" ...


> cust_profile

# A tibble: 5 × 4

     ID gender   age  name

  <chr>  <chr> <dbl> <chr>

1    c1      F    30   Kim

2    c2      M    28   Lee

3    c3   <NA>    46  Park

4    c4      M    65  Moon

5    c5      F    38  Choi






엑셀 데이터 크기가 커지면 행과 열의 범위(range)를 찾아서 정확히 입력하기가 번거로운 경우도 있을 텐데요, 아래처럼 처음의 2개의 행(row)은 무시하고(skip), 3번재 행부터 엑셀에서 데이터를 불러오라고 설정을 해주어도 결과는 똑같게 데이터를 불러올 수 있습니다. 


> # importing excel file using 'skip' option

> cust_profile_2 <- read_excel("C:/Users/Administrator/Documents/cust_profile.xlsx", # path

+                            sheet = "cust_profile", # sheet name to read from

+                            skip = 2, # Minimum number of rows to skip before reading anything

+                            col_names = TRUE, # TRUE to use the first row as column names

+                            col_types = "guess", # guess the types of columns

+                            na = "NA") # Character vector of strings to use for missing values

> cust_profile_2

# A tibble: 5 × 4

     ID gender   age  name

  <chr>  <chr> <dbl> <chr>

1    c1      F    30   Kim

2    c2      M    28   Lee

3    c3   <NA>    46  Park

4    c4      M    65  Moon

5    c5      F    38  Choi

 



다음으로 RStudio GUI 를 사용해서 클릭 몇 번으로 간단하게 엑셀 데이터 불러오는 방법을 소개하겠습니다. 


  (3) RStudio GUI 를 사용해서 엑셀 데이터 불러오기


(3-1) RStudio 우측 상단의 'Environments' 창에서 'Import Dataset' 메뉴를 선택한 후에, 'From Excel...' 하위 메뉴를 선택해보세요. 



(3-2) RStudio 의 'Import Excel Data' 창에서 왼쪽 하단의 'Import Options:' 화면에서 옵션을 입력하여 엑셀 데이터 불러오기



'Import Options:' 화면이 잘 안보일 것 같아서 아래에 확대해서 R Script 와 비교할 수 있도록 번호를 같이 표기해보았습니다. 


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

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



저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

Windows OS를 사용하는 곳에서 20~30명 정도의 대형 강의실에서 R 교육을 진행한다거나 Rstudio를 처음 설치해서 사용하는 Windows OS 사용자 중에서 보면 Rstudio 에서 에러가 난다든지, ggplot2 를 실행해도 그래프가 안그려진다든지 하는 경우가 있습니다.  


이럴 경우 제일 처음 확인해보면 도움이 되는게 바로 '사용자 계정이 한글'인지 여부 입니다. 


Rstudio 는 계정이 한글인 경우 경로를 잘 인식하지 못하는 문제점을 가지고 있습니다.  


만약 계정이 한글(예: '홍길동', '00회사')로 되어 있을 경우에는 '사용자 계정을 영어로 변경' 해주면 Rstudio 문제가 해결됩니다. 


사용자 계정을 영어로 변경하는 방법은 아래 순서를 참고하세요. 




[ Windows 의 사용자 계정을 한글에서 영어로 변경하기 ]


  (1) 시작  > (2) 제어판  > (3) 사용자 계정  > (4) 다른 계정 관리  > (5) 새 계정 만들기  

      > (6) 계정 이름 지정 (in English!!) 및 계정 유형 선택

 




(1) 시작 >  (2) '제어판' 선택





(3) (제어판에서) '사용자 계정' 선택





(4) (제어판의 사용자 계정 변경 화면에서) '다른 계정 관리' 메뉴 선택





(5) (하단 메뉴 중에서)  '새 계정 만들기' 메뉴 선택





(6) '계정이름을 영어로 지'해주고 '관리자'로 계정 유형 선택하기

   (계정이름은 반드시 '영어!', 'ENGLISH!!'로 지정)




이렇게 사용자 계정을 한글에서 영어로 변경해놓고 Rsutdio 껐다가 다시 실행시켜보세요. 


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


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



저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

요즘 하도 바빠서 포스팅 못한지 거의 3주째 되어가는것 같네요. ㅜ_ㅜ

게다가 올해 초부터는 Python 연재한다고 R 포스팅은 후순위로 밀려버렸네요. ㅠ_ㅠ

 

오랜만에 R 데이터전처리 영역에서 사용하는 간단한 팁 하나 포스팅하겠습니다.

 

R에서는 '#' 이후의 문자는 모두 무시해버립니다.  있어도 없는 척, 모른 척 해버리는 것이지요. 그래서 '#'을 사용해서 부연설명, 주석을 달면 유용합니다.

 

그런데 말입니다 ('그것이 알고 싶다' 사회자 목소리 버전으로다가.... 자못 심각...-_-;),

 

외부 데이터셋을 read.table() 함수를 사용해서 R로 읽어들이려고 하는데요, 그 외부 데이터셋에 하필이면 '#'이 들어가 있는 겁니다. 아래의 예제 데이터셋처럼 말이지요.  첫번째 행(row)은 변수 이름 (header)이구요, 네번째와 다섯번째 행에 파란색으로 표신된 '#' 부호가 보이시지요? 

 

부가설명을 달기 위한 목적으로 '#'을 썼을 수도 있구요, '#'이 특정 코드값의 하나여서 사용했을 수도 있구요, 입력 실수로 '#'을 썼을 수도 있구요, 이유야 여러가지가 있을 수 있겠습니다.

 

 

[ 원소로 '#' 부호가 들어가 있는 데이터셋 예시]

 

 

 

* 위 예제 데이터셋(comment_char.txt) 첨부 =>   comment_char.txt

 

 

 

위와 같이 데이터셋의 원소로 '#' 문자가 포함된 외부 데이터셋을 read.txt() 함수를 사용해서 읽어들이려고 하면 아래와 같이 '#'이 들어있는 행의 원소의 갯수가 모자란다는 Error 메시지가 뜹니다.

 

"Error in scan line 3 did not have 5 elements"

 

 

> # If dataset contains '#' character, then an error will be raised
> # with a message as follows : "line xx did not have xx elements"
> aa <- read.table("C:/Users/Administrator/Documents/comment_char.txt", 
+                  sep = ",", 
+                  header = TRUE, 
+                  stringsAsFactors = FALSE)
Error in scan(file = file, what = what, sep = sep, 
quote = quote, dec = dec, : line 3 did not have 5 elements

 

 

 

 

이때 fill = TRUE 옵션을 사용해주면 일단 원소가 모자라서 아예 불러들이지 못하는 오류는 피해갈 수 있기는 합니다.  하지만 아래에 aa 라는 이름으로 외부 데이터를 불어와서 만든 데이터프레임을 열어보면 '#'부터 해서 '#' 이후의 원소들은 전부 'NA'로 결측값 처리 되었음을 알 수 있습니다.

 

 

> # Every elements after '#' will be 'NA' because R interprete '#' as a comment character
> aa <- read.table("C:/Users/Administrator/Documents/comment_char.txt", 
+                  sep = ",", 
+                  header = TRUE, 
+                  stringsAsFactors = FALSE, 
+                  fill = TRUE)

 

 

 

 

 

 

만약 '#'을 무시하지 않고 그냥 일반 문자열(string)으로, 문자형(character type)으로 인식해서 있는 그대로 불어들이고 싶다면, 그래서 '#' 뿐만 아니라 '#' 이후의 데이터들도 정상적으로 전부 다 불러읽어오고 싶을 때면 comment.char = "" 옵션을 추가해주면 됩니다.  이러면 fill = TRUE 옵션을 별도로 사용하지 않아도 정상적으로 '#'을 포함하고 있는 데이터셋도 잘 불러들일 수 있습니다.  위의 'aa' 데이터셋(fill = TRUE)과 아래의 'bb' 데이터셋(comment.char = "")을 비교해보시면 쉽게 이해하실 수 있을 겁니다.

 

 

> # It will turn off the interpretaton of comments '#'
> bb <- read.table("C:/Users/Administrator/Documents/comment_char.txt", 
+                  sep = ",", 
+                  header = TRUE, 
+                  stringsAsFactors = FALSE, 
+                  comment.char = "")

 

 

 

 

 

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


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

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

이번 포스팅에서는 dplyr 패키지의 bind_rows() 함수와 bind_cols() 함수에 대해서 알아보겠습니다.

 

dplyr 패키지의 bind_rows() 함수는 두개 이상의 데이터 프레임을 행 기준(위 - 아래 - 아래 ...)로 합칠 때 사용하는 함수이며, {base] 패키지의 rbind() 함수와 유사한 기능을 수행합니다.

 

dplyr 패키지의 bind_cols() 함수의 두개 이상의 데이터 프레임을 열 기준(왼쪽 - 오른쪽 - 오른쪽 ...)로 합칠 때 사용하는 함수이며, {base} 패키지의 cbind() 함수와 유사한 기능을 수행합니다.

 

dplry 패키지는 데이터 프레임의 데이터 구조에 특화된 패키지이므로 아래에 제시한 예시는 모두 데이터 프레임에만 해당이 됩니다.

(참고로, {base} 패키지의 rbind(), cbind() 는 두 개 이상의 vector에 대해서도 실행이 되며, vector를 rbind(), cbind() 할 경우 matrix 를 반환합니다.  {dplyr} 의 bind_rows(), bind_cols()를 vector 에 적용하면 실행 안됨.)

 

 

[ bind_rows(), bind_cols() in {dplyr}
: 효과적으로 다수의 데이터 프레임 행 기준, 열 기준으로 합치기 ]

 

 

 

 

1. bind_rows() : 다수의 데이터 프레임을 행 기준으로 합치기 (binding multiple data frames by row)

 

기본적인 사용법은 bind_rows(dataframe 1, dataframe 2, ...) 입니다.  {base} 패키지의 rbind()와 동일합니다.

 

 

> ##------------------------------------------------------ > ## R {dplyr} package > ## bind() : Efficiently bind multiple data frames by row and column. > ##------------------------------------------------------ > # install.packages("dplyr") > library(dplyr) > > # making 2 data.frame examples > df_1 <- data.frame(x = 1:3, y = 1:3) > df_2 <- data.frame(x = 4:6, y = 4:6) > > # rbind() in {base} package > rbind(df_1, df_2) x y 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 > > # bind_rows in {dplyr} package > bind_rows(df_1, df_2) x y 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6

 

 

 

 

{base} 패키지의 rbind()와 동일한 결과를 반환한다면 왜 굳이 dplyr 패키지의 bind_rows() 함수를 써야할 필요가 있을까 싶을 것입니다. 

 

{base} 패키지의 rbind() 대비 dplyr 패키지의 bind_rows() 가 좋은 점 세가지를 소개하겠습니다.

(꼭 dplyr 패키지 영업사원 된 듯한 기분...ㅋㅋ)

 

  • (1-1) 열(columns)이 서로 동일하지 않아도 행(rows) 기준으로 합칠 수 있음
    (--> 합치는 과정에서 열이 달라서 빈 자리는 NA 값 처리됨)
  • (1-2) 'id' 매개변수를 사용해 합쳐지기 전 데이터 프레임의 원천을 알 수 있음
    (--> source를 알 수 있는 새로운 변수 생성함)
  • (1-3) dplyr 패키지의 처리 속도가 {base} 패키지 대비 상대적으로 엄청나게 빠름
    (100배 이상 빠름!!!)

 

위 세가지 dplyr 패키지의 bind() 함수의 좋은 점 세 가지를 예를 들어서 설명하겠습니다.

 

 

 

(1-1) 열(columns)이 서로 동일하지 않아도 행(rows) 기준으로 합칠 수 있음
       (--> 합치는 과정에서 열이 달라서 빈 자리는 NA 값 처리됨)

 

 

> # In case columns do not match b/w data frames
> df_1 <- data.frame(x = 1:3, y = 1:3)
> df_3 <- data.frame(x = 7:9, z = 7:9)
> 
> # rbind(): Columns need to match
> # if not, Error in match.names(clabs, names(xi))
> rbind(df_1, df_3) # Not run
Error in match.names(clabs, names(xi)) : 
  names do not match previous names
> 
> # bind_rows(): Columns don't need to match when row-binding
> bind_rows(df_1, df_3)
  x  y  z
1 1  1 NA
2 2  2 NA
3 3  3 NA
4 7 NA  7
5 8 NA  8
6 9 NA  9

 

 

 

 

 

(1-2) 'id' 매개변수를 사용해 합쳐지기 전 데이터 프레임의 원천을 알 수 있음
(--> source를 알 수 있는 새로운 변수 생성함)

 

 

> # When you supply a column name with the `.id` argument, a new
> # column is created to link each row to its original data frame
> df_1 <- data.frame(x = 1:3, y = 1:3)
> df_2 <- data.frame(x = 4:6, y = 4:6)
> df_3 <- data.frame(x = 7:9, z = 7:9)
> 
> bind_rows(list(grp_1 = df_1, grp_2 = df_2, grp_3 = df_3), .id="group_id")
  group_id x  y  z
1    grp_1 1  1 NA
2    grp_1 2  2 NA
3    grp_1 3  3 NA
4    grp_2 4  4 NA
5    grp_2 5  5 NA
6    grp_2 6  6 NA
7    grp_3 7 NA  7
8    grp_3 8 NA  8
9    grp_3 9 NA  9

 

 

 

 

 

 

(1-3) dplyr 패키지의 처리 속도가 {base} 패키지 대비 상대적으로 엄청나게 빠름
(100배 이상 빠름!!!)

 

system.time() 함수를 사용해서 {base} 패키지의 rbind() 함수와 {dplyr} 패키지의 bind_rows() 함수의 CPU 실행시간을 알아보겠습니다.(사용자 + 시스템 = elapsed 이므로 elapsed 결과를 비교하면 됨. 실행을 시킬 때마다 숫자가 조금씩 달라지기는 하지만, 차이가 워낙 커서 경향성이 뒤집히지는 않을 것이므로 한번만 실행시켜보고 비교해보겠음

 

데이터 프레임의 행의 개수가 너무 작으면 차이가 티가 잘 안나므로, 백만개의 행을 가진 데이터 프레임 두 개를 만들어서 비교해보겠습니다.

 

{base} 패키지의 rbind() 가 7.85초 걸렸고, {dplyr} 패키지의 bind_rows()는 0.03초가 걸렸으니 261배 차이가 났군요. (2.6배나 26배가 아니라 261배 차이임. 안 놀라는 사람은 뭡니까? ⊙⊙;)

 

크기가 작은 데이터라면 rbind()와 bind_rows() 함수의 실행 시간 차이를 아마 거의 느끼지 못할 것입니다만, 대용량 데이터의 경우 C 기반으로 짜여진 {dplyr} 패키지가 훨~씬 빠르고 처리 속도 차이가 피부로 느껴질 것입니다.  

 

 

> # system.time : rbind() in {base} vs bind_rows() in {dplyr} > # System operation time of dplyr bind_rows is extremely shorter than that of rbind > one <- data.frame(c(x = c(1:1000000), y = c(1:1000000))) > two <- data.frame(c(x = c(1:1000000), y = c(1:1000000))) > > system.time(rbind(one, two)) # elapsed 7.85 사용자 시스템 elapsed 7.02 0.16 7.85 > > system.time(bind_rows(one, two)) # elapsed 0.03 사용자 시스템 elapsed 0.02 0.02 0.03

> 7.85/0.03
[1] 261.6667

 

 

 

 

2. bind_cols() : 다수의 데이터 프레임을 열 기준으로 합치기 (binding multiple data frames by columns)

 

cbind()와 기능 및 활용법은 유사하며, 기본 활용법은 bind_cols(dataframe 1, dataframe 2, ...) 입니다.

 

 

> # binding data frames by column
> bind_cols(df_1, df_2, df_3)
  x y x y x z
1 1 1 4 4 7 7
2 2 2 5 5 8 8
3 3 3 6 6 9 9

 

 

 

 

{base} 패키지의 cbind()와 {dplyr} 패키지의 bind_cols() 의 CPU 실행시간을 비교해보겠습니다. 

(참고로, 실행시킬 때마다 아주 조금씩 달라짐)

 

{base} 패키지의 cbind()는 0.61초, {dplyr} 패키지의 bind_cols() 는 0.0001초가 걸린걸 보면, 역시 {dplyr} 패키지가 월등히 빠름을 알 수 있습니다.

 

 

> # system.time comparison : cbind() vs. bind_cols()
> one <- data.frame(c(x = c(1:1000000), y = c(1:1000000)))
> two <- data.frame(c(x = c(1:1000000), y = c(1:1000000)))
> 
> system.time(cbind(one, two)) # elapsed 0.61
 사용자  시스템 elapsed 
   0.47    0.03    0.61 
> system.time(bind_cols(one, two)) #elapsed 0
 사용자  시스템 elapsed 
      0       0       0

 

 

 

 

bind_cols() 함수는 서로 합칠 데이터 프레임의 행(rows)의 개수가 서로 같아야만 하므로 주의를 요합니다.  만약 행의 개수가 서로 다를 경우에는 "Error in eval(substitute(expr), envir, enclos) :  incompatible number of rows (4, expecting 3)" 에러가 납니다.

 

> # bind_cols() : Rows do need to match when column-binding > bind_cols(data.frame(x = 1:3), data.frame(y = 1:3)) # run x y 1 1 1 2 2 2 3 3 3 > > bind_cols(data.frame(x = 1:3), data.frame(y = 1:4)) # Not run Error in eval(substitute(expr), envir, enclos) : incompatible number of rows (4, expecting 3)

 

 

 

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

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ♡'를 눌러주세요. ^^

 

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

R의 dplyr 패키지에 대해서 연재를 하고 있는데요, dplyr 패키지 매뉴얼에 보니 유용한 함수들이 여럿 더 있네요. 그동안 dplyr 패키지 함수들에 대해서 소개했던것 외에 눈에 띄는 함수들을 서너번 더 나누어서 소개할까 합니다.

 

두 개의 데이터 프레임이 있을 때 "동일한 값을 가진 데이터 프레임인가?" 아니면 "서로 다른 값을 가진 데이터 프레임인가?"를 확인할 때 사용할 수 있는 {dplyr} 패키지의 all_equal() 함수를 소개하겠습니다.

 

비교하고자 하는 두 개의 데이터 프레임에 관측치와 변수가 모두 몇 개씩 밖에 안된다면 "눈으로 확인"하는 것도 가능할 것입니다.  하지만 관측치가 몇 백개를 넘어가거나, 혹은 변수가 몇 백개를 넘어간다면 육안으로 일일이 확인한다는게 피같은 시간을 낭비하는 것이 되며, 또 실수를 할 수도 있습니다.  더욱이 "데이터셋의 값은 동일한데 관측치의 순서만 다르건", 혹은 "데이터셋의 값은 동일한테 변수의 순서만 다른" 경우, 혹은 "데이터셋의 값은 동일한데 관측치의 순서와 변수의 순서만 서로 다른" 경우, 이건 뭐 육안으로 확인한다는게... 미치는거지요. ㅋㅋ

 

 

 

 

 

이때 간단하게 두 데이터 프레임 내의 값들이 서로 같은지를 간단하게 비교해서 TRUE, FALSE 를 반환해주는 함수가 dplyr 패키지의 all_equal() 함수입니다.

 

 

# all_equal() usage

 

all_equal(targetcurrent, # two data frame to compare

            ignore_col_order = TRUE, # Should order of columns be ignored?

            ignore_row_order = TRUE, # Should order of rows be ignored?
            convert = FALSE # Should similar classes be converted?)

 

 

 

 

{datasets} 패키지에 내장되어 있는 (1) mtcars 데이터 프레임 원본(target)과 (2) mtcars 의 행과 열을 임의로 순서만 바꾼 (단, 데이터는 변동없이 동일함) ran_sam_mtcars 데이터 프레임(current)을 만들어서 all_equal() 함수를 사용하여 비교를 해보겠습니다.

 

 

(1) 비교할 기준 target 데이터 프레임 : mtcars (원본)

 

 

 

> ##-----------------------------------------------------------
> ## R {dplyr} package 
> ## > all_equal : flexible equality comparison for data frames
> ##-----------------------------------------------------------
> # install.packages("dplyr") # for the first time user of dplyr package
> library(dplyr)
> 
> # original data frame : mtcars
> 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

 

 

 

 

 

(2) 비교하고자 하는 현재(current) 데이터 프레임 만들기 : ran_sam_mtcars

(행과 열을 무작위로 섞어 놓았으며, 데이터 삭제나 추가 등의 변동은 없이 동일함)

 

위의 (1)번 원본 target과 동일한 데이터 프레임인데요, 눈으로 동일한 데이터셋인지 확인하라면 어떤 마음이 들까요? (노트북 던지고 싶은 마음? -,-;;;)

 

 

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

 

 

 

 

 

(3) 두 개의 데이터 프레임, 즉 (1) target 데이터 프레임 mtcars 와, (2) current 데이터 프레임 ran_sam_mtcars 이 동일한 데이터를 가지고 있는건지 아닌지를 {dplyr} 패키지의 all_equal() 함수를 써서 확인해보기

 

 

> # all_equal() : flexible equality comparison for data frames
> # By default, ordering of rows and columns ignored
> all_equal(mtcars, ran_sam_mtcars)
[1] TRUE

 

 

행과 열의 순서만 바뀌었을 뿐 값 자체에 대한 변동은 없으므로 당연히 "TRUE"라고 나와야 겠지요.

 

비교하는 두 개의 데이터 프레임이 행의 순서가 같은지(ignore_row_order = TRUE), 혹은 열의 순서가 같은지(ignore_col_order = TRUE)는 확인하지 않고 무시하게끔 default 옵션이 설정되어 있습니다.

 

 

 

 

(4) 행의 순서가 같은지 확인해보기
    : all_equal
(target, current, ignore_row_order = FALSE),

    열의 순서가 같은지 확인해보기
    : all_equal(
target, current, ignore_col_order = FALSE)

 

 

> # To check the equality of row's sequence : ignore_row_order = F
> all_equal(mtcars, ran_sam_mtcars, ignore_row_order = FALSE)
[1] "Same row values, but different order"
> 
> 
> # To check the equality of column's sequence : ignore_col_order = F
> all_equal(mtcars, ran_sam_mtcars, ignore_col_order = FALSE)
[1] "Same column names, but different order"

 

 

 

 

마지막으로, convert 매개변수는 두 개의 데이터 프레임 내 동일한 변수 이름을 가지고 있고, 값도 서로 같은데, 속성만 하나는 "factor", 하나는 "character"인 경우 "factor"를 "character"로 변경해주어서 => all_equal() 함수 최종 평가 결과를 "TRUE"로 반환해줄 수 있도록 자동으로 속성 변환해서 비교해주는 기능을 수행합니다.

 

[Reference] https://cran.r-project.org/web/packages/dplyr/dplyr.pdf

 

 

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

 

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

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

지난번 포스팅에서는 R dplyr 패키지의 Window function 중에서 행 전체를 위로 올릴 때 사용하는 lead() 함수, 행 전체를 아래로 내릴 때 사용하는 lag() 함수에 대해서 알아보았습니다.

(☞ 바로 가기 : http://rfriend.tistory.com/242 )

(* 참고 : Window function : n개의 행을 input으로 받아서 n개의 행을 output으로 반환하는 함수)

 

이번에는 R dplyr 패키지의 Window funciton 에 대한 마지막 포스팅으로

 - (3) Cumulative aggregates : cumall() 함수, cumany() 함수, cummean() 함수와 

 - (4) Recycled aggrerates 에 대해서 소개하겠습니다.

 

 

[ Types of Window functions in {dplyr} package ]

 

 

 

예제로 사용할 데이터를 먼저 간단히 소개하고 나서 cumulative aggretages, recycled aggregates 함수로 넘어가겠습니다. 예제로 사용할 데이터는 MASS 패키지에 내장되어 있는 Cars93 데이터프레임의 차종(Type), 가격(Price) 변수입니다.

 

 
> ##------------------------------------------------
> ## R dplyr package 
> ## > window function - Cumulative aggregates 
> ##  : cumany(), cumall(), cummean()
> ##------------------------------------------------
> 
> # install.packages("dplyr")
> library(dplyr)
> 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 ...
> table(Cars93$Type)

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9

 

 

 

 

직관적으로 결과를 비교하기 편하도록 Cars93 데이터프레임에서 (a) 차종(Type), 가격(Price) 의 두개의 변수만 선별하고, (b) 차종(Tpye) 중에서 관측치 개수가 적은 'Large'와 'Van' 만 남긴 후에, (c) 차종(Type), 가격(Price) 를 기준으로 오름차순으로 정렬하여 "Cars93_1" 이라는 새로운 데이터프레임을 만들어 보겠습니다.

 

 
> # select 'Type', 'Price' variable from Cars93 dataframe
> select <- dplyr::select # to avoid conflict select() function b/w dplyr and MASS
> 
> Cars93_1 <- Cars93 %>% 
+   select(Type, Price) %>% 
+   filter(Type %in% c("Large", "Van")) %>% 
+   arrange(Type, Price)
> 
> Cars93_1
    Type Price
1  Large  18.4
2  Large  18.8
3  Large  19.3
4  Large  20.7
5  Large  20.8
6  Large  20.9
7  Large  23.7
8  Large  24.4
9  Large  29.5
10 Large  34.7
11 Large  36.1
12   Van  16.3
13   Van  16.6
14   Van  19.0
15   Van  19.1
16   Van  19.1
17   Van  19.5
18   Van  19.7
19   Van  19.9
20   Van  22.7

 

 

 

 

간소화해서 새로 만든 Cars93_1 데이터프레임에서 차종(Type) 별로 최소(min) 가격, 평균(mean) 가격, 중앙값(median) 가격, 최대(max) 가격을 구해보겠습니다.

 

> # calculating min/mean/median/max Price by Type
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   summarise(n = n(), # number by Type
+             Price_min = min(Price, na.rm = T), 
+             Price_mean = mean(Price, na.rm = T), 
+             Price_median = median(Price, na.rm = T), 
+             Price_max = max(Price, na.rm = T))
# A tibble: 2 x 6
    Type     n Price_min Price_mean Price_median Price_max
  <fctr> <int>     <dbl>      <dbl>        <dbl>     <dbl>
1  Large    11      18.4       24.3         20.9      36.1
2    Van     9      16.3       19.1         19.1      22.7

 

 

 

 

자, 이제 데이터셋 준비가 다 되었으니 본론으로 넘어가보겠습니다. 

 

함수에 대해서 말로 설명해놓기는 했습니다만, 잘 안 와닿을 것 같습니다.  예제를 보면서 각 함수가 어떤 기능을 하는지 찬찬히 살펴보시면 도움이 될거 같아요.  위의 요약통계량을 보니 가격 최소값 '18'을 조건으로 cumall()과 cumany()를 사용하면 "Large"와 "Van"이 서로 다른 결과를 반환하겠네요.

 

 

 (1) Cumulative aggregates : cumall() 함수, cumany() 함수, cummean() 함수

 

(1-1) cumall () 함수 : 조건을 모두 만족(cumulative &&)하는 (그룹의) 전체 행 반환

 

 
> # cumall()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(cumall(Price > 18))
Source: local data frame [11 x 2]
Groups: Type [1]

     Type Price
   <fctr> <dbl>
1   Large  18.4
2   Large  18.8
3   Large  19.3
4   Large  20.7
5   Large  20.8
6   Large  20.9
7   Large  23.7
8   Large  24.4
9   Large  29.5
10  Large  34.7
11  Large  36.1

 

 

 

좀더 이해하기 쉽도록 아래에 원래의 Cars93_1 데이터프레임과 차종(Type)별 cumall(Price > 18) 조건으로 선별(filtering)을 한 후의 결과를 비교해놓았습니다.  "Van" 차종의 경우 가격(Price)이 18 이하인 관측치(12번, 13번) 2개 존재하므로 cumall(Price > 18) 에서 제시한 "모든 관측치가 만족(%%)" 조건을 만족하지 않으므로 "모든 행이 제외"되었습니다.

 

 

 

 

(1-2) cumany() 함수 : 조건을 만족(cumulative ||)하는 (그룹 내) 행만 반환

 

 
> # cumany()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(cumany(Price > 18))
Source: local data frame [18 x 2]
Groups: Type [2]

     Type Price
   <fctr> <dbl>
1   Large  18.4
2   Large  18.8
3   Large  19.3
4   Large  20.7
5   Large  20.8
6   Large  20.9
7   Large  23.7
8   Large  24.4
9   Large  29.5
10  Large  34.7
11  Large  36.1
12    Van  19.0
13    Van  19.1
14    Van  19.1
15    Van  19.5
16    Van  19.7
17    Van  19.9
18    Van  22.7

 

 

 

 

좀더 이해하기 쉽도록 아래에 원래의 Cars93_1 데이터프레임과 차종(Type)별 cumany(Price > 18) 조건으로 필터링한 결과를 비교해놓았습니다. 12번째, 13번째 행의 "Van" 차종의 관측치가 cumany(Price > 18) 조건을 만족하지 않으므로 제외되었습니다.

 

 

 

 

 

(1-3) cummean() 함수 : (그룹별로) 행을 하나씩 이동해가면서 누적으로 평균 반환

 

mutate() 함수와 함께 사용해서 새로운 cummean.Price 변수를 만들어보겠습니다.

 

 
> # cummean()
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   mutate(cummean.Price = cummean(Price))
Source: local data frame [20 x 3]
Groups: Type [2]

     Type Price cummean.Price
   <fctr> <dbl>         <dbl>
1   Large  18.4      18.40000
2   Large  18.8      18.60000
3   Large  19.3      18.83333
4   Large  20.7      19.30000
5   Large  20.8      19.60000
6   Large  20.9      19.81667
7   Large  23.7      20.37143
8   Large  24.4      20.87500
9   Large  29.5      21.83333
10  Large  34.7      23.12000
11  Large  36.1      24.30000
12    Van  16.3      16.30000
13    Van  16.6      16.45000
14    Van  19.0      17.30000
15    Van  19.1      17.75000
16    Van  19.1      18.02000
17    Van  19.5      18.26667
18    Van  19.7      18.47143
19    Van  19.9      18.65000
20    Van  22.7      19.10000

 

 

 

 

group_by(Type) 함수를 같이 써서 차종(Type)별로 행을 하나씩 아래로 내려가면서 그룹 내 첫 행부터 행당 행까지의 누적 관측치를 모두 사용해서 평균(cumulative mean)을 구했음을 알 수 있습니다. (말로 설명하려니 힘든데요, 말로 된 설명만 봐서는 무슨 말이지 좀 어렵지요? ^^;;;  아래 설명 그림 참고하세요)

 

 

 

 

Cumulative aggregates 소개는 마치고, 이제 Recycled aggregates로 넘아가보겠습니다.

평균이나 중앙값과 같이 (그룹별) 요약통계량을 생성한 후에, 이 요약통계량 벡터를 재활용(Recycling)해서 조건을 부여해 filtering 하는 방법을 아래에 소개합니다.  이름은 Recycled aggregates 라고 거창하게 붙이긴 했는데요, 아래의 예시를 보시면 뭐 별거 없습니다.

 

 

(2) Recycled aggregates

     : group_by(factor) %>% filter(dataframe, x > mean(x)),
     : group_by(factor) %>% filter(dataframe, x > median(x))
 

 

(2-1) group_by(factor) %>% filter(dataframe, x > mean(x))
       : 그룹별로 평균 값(mean)보다 큰 행(rows)만 선별

 

 

> ##------------------------------------------------ > ## R dplyr package > ## > window function - Recycled aggregates > ## : filter(dataframe, x > mean(x)) > ## : filter(dataframe, x > median(x)) > ##------------------------------------------------ > Cars93_1 %>% + group_by(Type) %>% + summarise(n = n(), # number by Type + Price_mean = mean(Price, na.rm = T), + Price_median = median(Price, na.rm = T)) # A tibble: 2 x 4 Type n Price_mean Price_median <fctr> <int> <dbl> <dbl> 1 Large 11 24.3 20.9 2 Van 9 19.1 19.1 > > > # filter(dataframe, x > mean(x)) > Cars93_1 %>% + group_by(Type) %>% + filter(Price > mean(Price)) Source: local data frame [8 x 2] Groups: Type [2] Type Price <fctr> <dbl> 1 Large 24.4 2 Large 29.5 3 Large 34.7 4 Large 36.1 5 Van 19.5 6 Van 19.7 7 Van 19.9 8 Van 22.7 >

 

 

 

 

 

(2-2) group_by(factor) %>% filter(dataframe, x > median(x))

      : 그룹별로 중앙값(median) 보다 큰 행(rows)만 선별

 

 

> # filter(dataframe, x > median(x))
> Cars93_1 %>% 
+   group_by(Type) %>% 
+   filter(Price > median(Price))
Source: local data frame [9 x 2]
Groups: Type [2]

    Type Price
  <fctr> <dbl>
1  Large  23.7
2  Large  24.4
3  Large  29.5
4  Large  34.7
5  Large  36.1
6    Van  19.5
7    Van  19.7
8    Van  19.9
9    Van  22.7

 

 

 

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

 

 

참고로, {base} package에 기본으로 내장되어 있는 함수들인
 - 누적 합 (cumulative sums) : cumsum()
 - 누적 곱 (cumulative products) : cumprod()
 - 누적 최소값 (cumulative minima) : cummin()
 - 누적 최대값 (cumulative maxima) : cummax()

에 대해서는 여기( ☞ http://rfriend.tistory.com/231 )를 참고하세요.

 

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

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

window function은 n개의 행을 input으로 받아서 n개의 행을 가진 output을 반환하는 함수를 말합니다.

 

지난번 포스팅에서는 dplyr package의 window function 중에서 Ranking and Ordering을 하는 함수들로서 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대해서 알아보았습니다. (바로가기 http://rfriend.tistory.com/241)

 

이번 포스팅에서는 dplyr 패키지의 window function 두번째 시간으로서 특정 칼럼의 행을 위로 올리거나(Lead) 아니면 내리는(Lag) 함수에 대해서 알아보겠습니다.

 

lead() 나 lag() 함수는 시계열 데이터를 분석할 때 많이 사용하는 편입니다. 특정 그룹id와 날짜/시간 기준으로 정렬(sorting)을 해놓은 다음에, lead() 나 lag() 함수를 가지고 행을 하나씩 내리구요, 직전 날짜/시간 대비 이후의 값의 변화, 차이(difference)를 구하는 식으로 말이지요.

(시계열분석에 특화된 package를 사용하면 더 편하기 하지만.... 데이터 프레임을 가지고 dplyr 패키지의 lead()나 lag() 함수 알아놓는것도 유용해요)

 

 

 

 

 

먼저 간단한 벡터를 가지고 lead()와 lag() 사용법을 소개하겠습니다.

 

 

(1) lead(x, n = 1L, default = NA, ...) in {dplyr} package

 

lead() 함수는 벡터 값을 n = 1L (양의 정수값) 의 값 만큼 앞에서 제외하고, 제일 뒤의 n = 1L 값만큼의 값은 NA 로 채워놓은 값을 반환합니다.  이때, n  표기는 생략할 수 있습니다.

 

 

> ##-------------------------------------------------
> ## R dplyr package : window function - lead and lag
> ##-------------------------------------------------
> 
> library(dplyr)
> 
> # lead()
> x <- c(1:10)
> 
> lead(x, n = 1)
 [1]  2  3  4  5  6  7  8  9 10 NA
> 
> lead(x, 2)
 [1]  3  4  5  6  7  8  9 10 NA NA

 

 

 

 

(2) lag(x, n = 1L, default = NA, ...) in {dplyr} package

 

lag() 함수는 lead() 함수와 정반대로 생각하시면 됩니다.  lag() 함수의 n = 1L(양의 정수값) 만큼 제일 앞자리부터 뒤로 옮기고, n = 1L 개수 만큼의 자리에 NA 값을 채워넣은 값을 반환합니다.

 

default = "." 처럼 특정 값을 설정해주면 NA 대신 새로 설정해준 값 혹은 기호가 채워진 값을 반환합니다. (아래 세번째 예의 경우 "."으로 빈 자리가 채워지면서 모든 값에 큰 따옴표("")가 붙으면서 character 형태로 바뀌었습니다)

 

 

> # lag()
> x <- c(1:10)
> 
> lag(x, n = 1)
 [1] NA  1  2  3  4  5  6  7  8  9
> 
> lag(x, 2)
 [1] NA NA  1  2  3  4  5  6  7  8
> 
> lag(x, 2, default = ".")
 [1] "." "." "1" "2" "3" "4" "5" "6" "7" "8"

 

 

 

 

 


 

[문제] 위의 x_df 데이터 프레임의 group 별로 직전 대비 직후 값의 차이가 가장 큰 값(max)과 가장 작은 값을 각각 구하시오. (What are the max and min difference values between x and lag(x) of x_df dataframe by group?)

 

 

(0) 예제 데이터 프레임 만들기

 

분석할 때 보통 벡터 보다는 데이터 프레임을 가지고 많이 하므로 예제 데이터 프레임을 하나 만들어보겠습니다.  'group'이라는 요인(factor)형 변수와 seq_no 이라는 시간 순서를 나타내는 변수, 그리고 각 group별로 5개씩의 관찰값을 가진 숫자형(numeric) 변수 x로 구성된 데이터 프레임입니다.

 

 

> ##-- make data frame as an example
> group <- rep(c("A", "B"), each = 5)
> seq_no <- rep(1:5, 2)
> set.seed(1234)
> x <- round(100*runif(10), 1)
> 
> x_df <- data.frame(group, seq_no, x)
> x_df
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(1) lag() 하려고 하는 기준대로 정렬이 안되어 있으면 -> 먼저 정렬(sorting) 부터!

 

예제로 사용하려고 sample(nrow()) 함수로 무작위로 순서를 섞어서 x_df_random 이라는 데이터 프레임을 만들어보았습니다.  dplyr 패키지의 arrange() 함수를 가지고 group, seq_no 기준으로 정렬을 해보겠습니다. 

 

 

> # if data frame is not ordered properly, 
> # then arrnage it first by lag criteria
> x_df_random <- x_df[sample(nrow(x_df)),]
> x_df_random
   group seq_no    x
7      B      2  0.9
5      A      5 86.1
3      A      3 60.9
10     B      5 51.4
2      A      2 62.2
9      B      4 66.6
6      B      1 64.0
1      A      1 11.4
8      B      3 23.3
4      A      4 62.3
> 
> x_df_seq <- arrange(x_df_random, group, seq_no)
> x_df_seq
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(2) mutate() 함수와 lag() 함수로 group_x, x_lag 변수 만들기

 

 

> # making lagged variable at data frame with mutate() and lag() > x_df_seq_lag <- mutate(x_df_seq, + group_lag = lag(group, 1), + x_lag = lag(x, 1)) > > x_df_seq_lag group seq_no x group_lag x_lag 1 A 1 11.4 <NA> NA 2 A 2 62.2 A 11.4 3 A 3 60.9 A 62.2 4 A 4 62.3 A 60.9 5 A 5 86.1 A 62.3 6 B 1 64.0 A 86.1 <- need to delete this row 7 B 2 0.9 B 64.0 8 B 3 23.3 B 0.9 9 B 4 66.6 B 23.3 10 B 5 51.4 B 66.6

 

 

 

 

(3) group 과 group_lag 의 값이 서로 다르면 그 행(row)은 filter() 함수로 제외하기

 

 

> # if group and group_lag are different, then delete the row
> x_df_seq_lag_2 <- x_df_seq_lag %>% 
+   filter(group == group_lag)
> 
> x_df_seq_lag_2
  group seq_no    x group_lag x_lag
1     A      2 62.2         A  11.4
2     A      3 60.9         A  62.2
3     A      4 62.3         A  60.9
4     A      5 86.1         A  62.3
5     B      2  0.9         B  64.0
6     B      3 23.3         B   0.9
7     B      4 66.6         B  23.3
8     B      5 51.4         B  66.6

 

 

 

 

(4) group별로 x와 x_lag 값의 차이가 가장 큰 값(max)과 가장 작은 값(min) 구하기

 

지지난번 포스팅에서 소개했던 group_by()와 summarise(max()), summarise(min()) 함수를 이용하면 되겠습니다.

 

> # select max and min of difference between x and x_lag
> x_df_seq_lag_2 %>% 
+   group_by(group) %>% 
+   summarise(max_lag = max(x - x_lag, na.rm = TRUE), 
+             min_lag = min(x - x_lag, na.rm = TRUE))
# A tibble: 2 x 3
   group max_lag min_lag
  <fctr>   <dbl>   <dbl>
1      A    50.8    -1.3
2      B    43.3   -63.1

 

 

 

그리 어렵지 않지요? 

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

 

다음번 포스팅에서는 dplry package window function 세번째로 시간으로 Cumulative aggregates 를 하는데 사용하는 cumall(), cumany(), cummean() 함수, 그리고 Recycled aggregates 에 대해서 알아보겠습니다.

 

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

(Tistory 가 포스팅별로 조회수를 알려주는 기능이 없다보니 '공감♡' 개수로 참고하려고)

 

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

R dplyr 패키지에 대해서 연재를 하고 있는데요, 번 포스팅에서는 Window Function 에 대해서 소개를 하겠습니다.

 

Window function 은 n개의 input을 받아서 n 개의 output을 반환하는 함수입니다.  순위(rank)를 구하는 함수나 누적합(cumulative sum)을 구하는 함수가 대표적인 Window function입니다.

 

이와 대비되는 Aggregation function n개의 input을 받아서 1개의 output 을 반환합니다.  요약통계량의 평균(mean), 합계(sum), 개수 세기(count) 등이 Aggregation function 에 속합니다.

 

 

[ Window function vs. Aggregation function ]

 

 

 

 

R의 {dplyr} package에서 사용할 수 있는 Window function에는 (1) Ranking and Ordering, (2) Lead and Lag, (3) Cumulative aggregates, (4) Recycled aggregates 등의 4가지 유형이 있습니다.

 

 

[ Types of Window functions in R {dplyr} package ]

 

 

 

차례대로 설명을 할 건데요, 한꺼번에 모두를 소개하기는 양이 꽤 많으니 이번에는 (1) Ranking and Ordering 의 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 함수에 대해서만 우선 소개해드리겠습니다.

 

 - row_number(), min_rank(), dense_rank() 의 3개 함수는 순위에 대한 index를 반환하고,  

 - cume_dist(), percent_rank() 의 2개 함수는 순위에 대한 '0~1' 사이의 비율을 반환하며,

 - ntile() 은 n개의 동일한 개수로 데이터셋을 나누어줍니다.

 

 

먼저 순위(ranking) 에 대한 index 를 반환해주는 함수 3개를 살펴보겠습니다. 동일한 값이 있을 경우에 이를 처리하는 방법이 함수별로 차이가 있습니다. 

 

말로 일일이 설명하기가 좀 어려운데요, 아래에 c(1, 1, 1, 5, 5, 9, 7) 의 동일한 값 '1'이 3개, '5'가 2개 존재하는 간단한 벡터를 가지고 함수별로 어떤 결과를 반환하는지 비교해보는 예를 준비했습니다.  색깔 칠해놓은 동일 값들을 어떻게 index 처리하는지 유심히 비교해보시고요, 분석 목적에 맞는 함수를 선택해서 사용하시기 바랍니다.

 

 

(1-1) row_number() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 2, 3, ...' 처리

 

 

##--------------------------------------------
## Window Functions in {dplyr} package
## (1) ranking and ordering window functions
##--------------------------------------------

library(dplyr)

# if there are ties, ties can be handled in several ways
x <- c(1, 1, 1, 5, 5, 9, 7)

# (1-1) row_number()
row_number(x)

 

 

 

[1] 1 2 3 4 5 7 6

 

 

 

 

(1-2) min_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 4, 4,...' 처리

 

 

# (1-2) min_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

min_rank(x)

 

 

[1] 1 1 1 4 4 7 6

 

 

 

 

(1-3) dense_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 2, 2,...' 처리

 

 

# (1-3) dense_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

 

dense_rank(x)

 

 

[1] 1 1 1 2 2 4 3

 

 

 

참고로 {base} 패키지의 rank() 함수도 동일한 기능을 제공하는데요, ties.method = c("average", "first", "random", "max", "min") 매개변수 별로 어떤 결과가 나오는 지는 http://rfriend.tistory.com/28  포스팅의 제일 하단 예시를 참고하시기 바랍니다.

 

 


 

 

아래 두개의 cume_dist(), percent_rank() 함수는 순위(ranking)에 대한 0~1 사이의 비율을 반환하는데요, 둘 사이에 미묘한 차이가 있으니 유심히 살펴보시기 바랍니다.

 

(1-4) cume_dist() : 현재 값보다 작거나 동일한 값의 순위(ranking) 상의 비율 (0~1)

 

 

# (1-4) cume_dist()
# : the proportion of values less than or equal to the current value
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)
cume_dist(x)

 

 

[1] 0.4285714 0.4285714 0.4285714 0.7142857 0.7142857 1.0000000 0.8571429

 

 

 

위의 결과가 어떻게 나왔냐 하면요, 전체 7개의 숫자 중에서 첫번째 순위에 속하는 '1'이 3개 있으므로 3/7 = 0.4285714 가 나온 것입니다.  두번째 순위에 속하는 '5'는 2개가 있으며, 5보다 작은 수로 첫번째 순위의 '1'이 3개가 있으므로 '5'의 2개와 ''1'의 3개를 합친 5개를 총 개수인 7로 나눈 5/7 = 0.7142857이 나온 것입니다. (말로 설명하기가 참 어렵네요. -_-;)

 

 

> 3/7 # ordering like as c(1, 1, 1)
[1] 0.4285714
> 5/7 # ordering like as c(1, 1, 1, 5, 5)
[1] 0.7142857
> 6/7 # ordering like as c(1, 1, 1, 5, 5, 7)
[1] 0.8571429
> 7/7 # ordering like as c(1, 1, 1, 5, 5, 7, 9)
[1] 1

 

 

 

참고로, "패키지명:::함수명"을 실행하면 R 함수의 내부 소스 코드 (generic function)를 볼 수 있습니다.

cume_dist() 함수는 {base} 패키지의 rank(x, ties.method = "max") 를 가져다고 총 개수로 나누어준 것임을 알 수 있습니다.

 

> dplyr:::cume_dist
function (x) 
{
    rank(x, ties.method = "max", na.last = "keep")/sum(!is.na(x))
}
<environment: namespace:dplyr>

 

 

 

 

 

(1-5) percent_rank() : min_rank() 기준의 순위(ranking)에 대한 비율(0~1)

 

 

# (1-5) percent_rank()
# : the percentage of the rank
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)

 

percent_rank(x)

 

 

[1] 0.0000000 0.0000000 0.0000000 0.5000000 0.5000000 1.0000000 0.8333333

 

 

 

percent_rank() 함수의 내부 소스 코드를 들여다보면 아래와 같이 dplyr패키지의 min_rank() 값에서 1을 뺀 후에, 이를 총 관측값 개수에서 1을 뺀 값으로 나누었군요.  소스 코드를 보지 않고서는 연산 로직을 알기가 쉽지 않은 경우입니다. (위 결과가 어떻게 나온건지 한참을 고민했네요... -_-;)

 

이제 왜 첫번째 순위의 '1'이 '0.0000000'이 나왔는지 아시겠지요?  min_rank(x) 가

[1] 1 1 1 4 4 7 6

의 값을 반환하므로 분자에 해당하는 (min_rank(x) - 1) = 1 - 1 = 0 이 되어, 전체 값이 '0'이 된 것입니다.

 

 

> dplyr:::percent_rank
function (x) 
{
    (min_rank(x) - 1)/(sum(!is.na(x)) - 1)
}
<environment: namespace:dplyr>

 

 

 

 


 

 

 (1-6) ntile() : 동일한 개수를 가진 n개의 sub 데이터로 나누기

 

Cars93 데이터프레임의 가격(Price)를 기준으로 정렬(ordering)이 된 상태에서 동일한 개수를 가진 4개의 sub group으로 나누려고 할 때 ntile() 함수를 쓰면 됩니다. mutate() 함수를 사용해서 quartile 이라는 새로운 칼럼을 생성한 후에 Cars93_quartile 이라는 새로운 데이터프레임을 생성해보는 예제를 만들어보았습니다.

 

 

# (1-6) ntile() : divides the data up into n evenly sized buckets
#Price per Price Quartiles (4 evenly sized buckets)

library(MASS) # to use Cars93 data frame

 

Cars93_quartile <- Cars93[ ,c("Manufacturer", "Model", "Type", "Price")] %>%
  mutate(quartile = ntile(Price, 4))

 

 

 

 

 

 

quartile 이라는 새로운 칼럼에 가격(Price)를 기준으로 순위 정렬이 된 상태에서 4등분된 buckets별로 몇 개씩 자동차가 들어가 있는지 한번 세어보겠습니다.  (summarise(n = n()), tally(), count() 함수 중에 편한거 사용하면 되겠습니다. )  1번째 bucket 에는 24개 자동차가 들어가 있고, 나머지 3개 bucket에는 23개씩 동일하게 들어가 있습니다. (총 93개로 정확히 4등분 할 수 없는 경우라서 그렇습니다)

 

 

# counting up by quartiles
Cars93_quartile %>% group_by(quartile) %>% summarise(n = n())

 

# A tibble: 4 x 2
  quartile     n
     <int> <int>
1        1    24
2        2    23
3        3    23
4        4    23

 

Cars93_quartile %>% group_by(quartile) %>% tally() # same result
Cars93_quartile %>% count(quartile) # same result

 

 

 

이상으로 dplyr 패키지의 Window function 중에서 첫번째로 ranking and ordering 관련 함수들인 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대한 소개를 마치겠습니다.  많은 도움이 되었기를 바랍니다.

 

다음번 포스팅에서는 dplyr 패키지의 Window function 의 두번째 시간으로 'Lead and Lag'에 대해서 알아보겠습니다.

 

 

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

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend

그동안 R dplyr package 의 기본 함수와 chaining operator (%>%) 에 대해서 알아보았습니다.

이번 포스팅에서는 R dplyr package를 사용해서 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package)를 해보겠습니다.  counting 하는 것은 기본 중의 기본이라서 탐색적분석(Exploratory Data Analysis) 할 때 수시로 사용하므로 알아두면 유용하겠지요?!

혹시 chaining operator (%>%, shift+ctr+M) 에 대해서 모르는 분은 http://rfriend.tistory.com/236 포스팅을 참고하세요.  

 R dplyr package 의 summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally(), count() 함수에 대해서 하나씩 예를 들면서 설명하겠습니다.  summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally() 함수는 group_by()를 chaining 해서 사용하며, 마지막의 count() 함수만 group_by() chaining 없이 사용합니다.

 

[ 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package) ]

 

예시에 사용할 데이터는 MASS package에 내장되어 있는 Cars93 데이터 프레임입니다.

 ##--------------------------------------------------
## counting things up by group, using dplyr package
##--------------------------------------------------

library(dplyr)
library(MASS)

str(Cars93)

 

차종(Type)별로 행의 개수 (차량의 개수)를 세어보겠습니다.

 
'data.frame':	93 obs. of  28 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 ...
 $ sub_yn            : num  0 0 0 0 0 0 0 0 0 0 ...

 

 

자, 이제 dplyr 패키지를 사용해서 실습을 해보시지요.

 

(1)  dataframe %>% group_by(factor) %>% summarise(n = n())

먼저 summarise(n = n()) 함수입니다. group_by() 와 함께 chaining 해서 사용하는 예시입니다.

 

# using summarise(n = n()) in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  summarise(n = n())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 

 (2) dataframe %>% group_by(factor) %>% summarise(n = n(), n_dist = n_distinct())

차종(Type)별로 자동차 대수(행의 개수)와 더불어서, 차종별로 유일한 제조회사(Manufacturer)의 개수를 세어서 n_distinct_maker 라는 새로운 변수를 추가해보겠습니다.

 

# adding the number of distinct manufacturers
# by using multiple summaries inside summarise()
# like summarize(n = n(), n_distinct = n_distinct())
Cars93 %>%
  group_by(Type) %>%
  summarize(n = n(),
            n_distinct_maker = n_distinct(Manufacturer)) 

 

 

# A tibble: 6 x 3
     Type     n       n_distinct_maker
   <fctr> <int>            <int>
1 Compact    16               15
2   Large    11               10
3 Midsize    22               20
4   Small    21               16
5  Sporty    14               12
6     Van     9                8

 

 

 

 (3) dataframe %>% group_by(factor) %>% tally()

 R dplyr에는 summarise(n = n()) 함수와 동일한 기능, 동일한 결과를 반환하는 또 다른 함수로 tally() 가 있습니다.  group_by() 와 함께 chaining 해서 사용합니다.

 

# using tally() in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  tally()

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 (4) dataframe %>% count(factor)

마지막으로 소개할 count(factor) 함수는 group_by(factor) chaining 없이 사용합니다.  위의 3개 보다 좀더 간단하긴 한데요, 해석이나 가독성 면에서 group_by() 가 들어가게 프로그램 짜는 것을 더 선호하는 사용자도 있을 듯 합니다. 

 

# using count() in {dplyr} package}
Cars93 %>%
  count(Type) # doing both grouping and counting (no need for group_by())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 


 

참고로, dplyr 패키지 말고도 {base} package의 table() 함수나, {sqldf} package의 sqldf() 함수를 사용해도 그룹별 관측치 개수 세기가 가능합니다.  아래 참고하세요.

 

(대안 1) {base} package의 table() 함수

counting 결과 제시 포맷이 위의 dplyr 패키지를 사용했을 때와는 다릅니다.  dplyr 패키지를 사용한 그룹별 행의 개수 세기에서는 차종(Type)이 별도 행, count 개수 n이 별도 행으로 제시가 되었었는데요, base 패키지의 table() 함수는 아래의 예시처럼 옆으로 차종이 죽~ 늘어서 있습니다.

 

## alternative : table()

# using table() in {base} package
table(Cars93$Type)

 

 

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 

 

 

table() 분석 결과는 데이터 프레임이 아니라 'table'입니다.

> str(table(Cars93$Type))
 'table' int [1:6(1d)] 16 11 22 21 14 9
 - attr(*, "dimnames")=List of 1
  ..$ : chr [1:6] "Compact" "Large" "Midsize" "Small" ...

 

 

(대안 2) {sqldf} 패키지의 sqldf() 함수

 

## alternative : sqldf() 

# using {sqldf} package
install.packages("sqldf")
library(sqldf)
sqldf('
      select Type, count(*) as n
      from Cars93
      group by Type
      order by Type
      ')

 

 
     Type  n
1 Compact 16
2   Large 11
3 Midsize 22
4   Small 21
5  Sporty 14
6     Van  9

 

 

sqldf 패키지의 sqldf() 함수에 대한 좀더 자세한 사용법은 http://rfriend.tistory.com/79  포스팅을 참고하세요.

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

 

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

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by R Friend R_Friend


티스토리 툴바