R) 파일 입출력 - 2

R) 파일 입출력 - 2

이번에는 csv 이외의 형식을 가진 파일에 대해서 알아보도록 하겠다. csv 이외에 접하게 되는 파일은 JSON, XML, TSV 등 다양하다. 이 외의 통계 프로그램 전용 파일과 이미지 파일 등은 다음 포스팅에서 다룰 예정이다. 그럼 다양한 파일 종류와 그에 맞는 패키지를 소개하고자 한다.

구분자가 없는 텍스트

파일 내부에 줄바꿈만 존재하고 각 줄에는 별다른 구분자가 없는 파일을 말하는데, 보통 시스템 로그 파일이 이런 형식에 속한다. 다음은 R로 주기적인 작업(cron job)을 실행시켰을 때 생성될 수 있는 로그 파일을 읽어오는 예시이다.

1
2
3
4
readLines("data/log_file_sample.log", n = 5)
## [1] "[1] \"log: 2020-03-05 17:00:02\"" "<MySQLResult:0,0,0>"
## [3] "[1] \"log: 2020-03-06 17:00:02\"" "<MySQLResult:0,0,0>"
## [5] "[1] \"log: 2020-03-07 17:00:02\""

예제 파일: log_file_sample.log

기본 함수인 readLines()를 사용할 수 있으며, n은 파일을 몇 줄 읽어올지 명시하는 인자이다. 이 로그파일은 전혀 정제가 되어있지 않으며 정말 단순하고 정직하게 R 콘솔창에서 출력되는 값을 그대로 받아 기록한 파일이 되겠다. 나중에 특정 R코드의 주기적 실행을 설정할 경우 이런식의 로그를 남겨 기록하게 될 것이다.

이런 로그 파일 이외에도 대화 내용이나 문장의 경우 비슷한 형식을 보인다. 다음은 카카오톡 대화방의 내용을 내보내기 해서 별도로 저장할 경우 확인할 수 있는 내용이다.

1
2
3
4
5
6
7
8
9
10
readLines("data/line_text_sample.txt", encoding = "UTF-8", warn = FALSE)
## [1] "[aaa] [오전 9:00] 별탈없는데 왜요?"
## [2] "[bbb] [오전 9:00] 홍대 ~ 사당 라인 제대로 밀렸어요"
## [3] "[ccc] [오전 9:02] 와...16일에 난리나겟는데여"
## [4] "[ddd] [오전 9:03] ㅋㅋㅋㅋㅋㅋ"
## [5] "[eee] [오전 9:11] 아 파업 관련해서 지하철이 밀리는건가..."
## [6] "[fff] [오전 9:13] 어후 당분간은 "
## [7] "[fff] [오전 9:13] 버스만타구다녀야되나.."
## [8] "[ggg] [오전 9:26] 치과"
## [9] "[ggg] [오전 9:26] 도착 30분전"

예제 파일: line_text_sample.txt

Windows 운영체제는 CP949(euc-kr)를 사용하고 Linux나 MAC 운영체제의 경우는 UTF-8을 사용한다. 지금 이 글을 작성한 컴퓨터는 Windows 운영체제를 사용하고 있고, 읽어들인 파일의 텍스트가 UTF-8로 인코딩 되어있기에 encoding = "UTF-8"이라는 코드를 추가하였다. 참고로 Linux나 MAC 운영체제를 사용하고 있다면 굳이 해당 코드를 추가하지 않아도 깔끔하게 읽어올 수 있다.

※인코딩 관련 보다 자세한 내용은 링크의문서 참조.

기본 함수 readLines()보다 강력한 함수는 readr 패키지의 read_lines()가 있으며 예제는 다음과 같다.

1
2
3
4
5
readr::read_lines("data/line_text_sample.txt", skip = 2, n_max = 4)
## [1] "[ccc] [오전 9:02] 와...16일에 난리나겟는데여"
## [2] "[ddd] [오전 9:03] ㅋㅋㅋㅋㅋㅋ"
## [3] "[eee] [오전 9:11] 아 파업 관련해서 지하철이 밀리는건가..."
## [4] "[fff] [오전 9:13] 어후 당분간은 "

예제 파일: line_text_sample.txt

이 함수는 보다 많은 기능을 제공하는데 쓸데없는 줄을 뛰어넘고(skip) 원하는 줄 부터 읽어올 수 있으며 해당 줄 부터 최대 몇 줄(n_max) 까지 읽을지 설정할 수 있다. 심지어 인코딩 부분도 알아서 잘 해결해주고 있는 것을 볼 수 있다. 그 외에도 유용한 기능이 많으니 도움말 문서를 살펴보면 되겠다.

특수한 구분자

파일에 기록되어있는 숫자나 문자를 쉼표(,)로 구분하는 파일은 CSV. 그럼 쉼표가 아니면 어떻게 될까?

별도의 특수기호

1
2
3
4
5
read.delim("data/delim_sample.txt", sep = "#", nrows = 3)
## AWS_ID TM TA Wind X.
## 1 108 2016-07-01 00 24.2 2.3 =
## 2 108 2016-07-01 01 24.3 2.3 =
## 3 108 2016-07-01 02 23.7 3.8 =

예제 파일: delim_sample.txt

read.delim() 함수의 뒤에 있는 delim은 delimiter의 앞 5글자를 딴 것이다. 그리고 sep = "#"은 데이터가 # 기준으로 구분되어 있으니 이대로 처리해달라는 뜻이다. 조금 더 쉽게 말하자면 separate by # 와 같다고 보면 되겠다. 그리고 쉼표 이외의 특정 구분자로 자료가 구분되어 있는 경우 이 함수를 사용하는 것이 적절하다고 할 수 있는데, 사실 이 함수는 read.csv()함수로 완전히 대체할 수 있다.

tsv

1
2
3
4
5
read.csv("data/tsv_sample.tsv", sep = "\t", nrows = 3)
## Ozone Solar.R Wind Temp Month Day
## 1 41 190 7.4 67 5 1
## 2 36 118 8.0 72 5 2
## 3 12 149 12.6 74 5 3

예제 파일: tsv_sample.tsv

csv는 쉼표라면 tsv는 탭(tab)이다. 띄어쓰기와는 또 다른 탭은 위 코드를 보면 알겠지만 \t 라고 지정해주어야 된다. 그리고 readr 패키지와 data.table 패키지로도 읽을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
readr::read_tsv("data/tsv_sample.tsv", n_max = 3)
## Parsed with column specification:
## cols(
## Ozone = col_double(),
## Solar.R = col_double(),
## Wind = col_double(),
## Temp = col_double(),
## Month = col_double(),
## Day = col_double()
## )
##
## # A tibble: 3 x 6
## Ozone Solar.R Wind Temp Month Day
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 41 190 7.4 67 5 1
## 2 36 118 8 72 5 2
## 3 12 149 12.6 74 5 3

data.table::fread("data/tsv_sample.tsv", nrows = 3)
## Ozone Solar.R Wind Temp Month Day
## 1: 41 190 7.4 67 5 1
## 2: 36 118 8.0 72 5 2
## 3: 12 149 12.6 74 5 3

예제 파일: tsv_sample.tsv

여기서 특이한 점은 data.table 패키지의 fread() 함수는 보편적으로 많이 사용하는 구분자를 자동 감지하여 처리하기 때문에 이 경우는 sep 인자에 별도의 값을 할당하지 않아도 되었다. 보다 자세한 내용은 해당 함수의 도움말 설명을 보도록 하자.

JSON

JSON은 JavaScript Object Notation으로 자바스크립트(JavaScript)에서 주로 사용될 것 같지만 다양한 곳에서 사용되며 XML() 파일 형식만큼 많이 활용되는 데이터 형식이라고 할 수 있다. 보다 자세한 내용은 링크를 참조하자.

R에는 rJson 패키지와 jsonlite 패키지가 있는데 보다 가볍게 사용할 수 있는 jsonlite 패키지를 사용한 예제는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
library("jsonlite")
df = read_json("data/JSON_sample.json")
head(df, 2)
## [[1]]
## [[1]]$Ozone
## [1] 41
##
## [[1]]$Solar.R
## [1] 190
##
## [[1]]$Wind
## [1] 7.4
##
## [[1]]$Temp
## [1] 67
##
## [[1]]$Month
## [1] 5
##
## [[1]]$Day
## [1] 1
##
##
## [[2]]
## [[2]]$Ozone
## [1] 36
##
## [[2]]$Solar.R
## [1] 118
##
## [[2]]$Wind
## [1] 8
##
## [[2]]$Temp
## [1] 72
##
## [[2]]$Month
## [1] 5
##
## [[2]]$Day
## [1] 2

예제 파일: tsv_sample.tsv

JSON 파일을 메모장으로 열어보면 다음과 같다.

메모장으로 확인한 JSON 파일

굉장히 보기 불편해 보이지만 해당 자료를 조금 정리하면 jsonlite 패키지의 toJSON() 함수를 사용한 것과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
toJSON(df[1:2], pretty = TRUE)
## [
## {
## "Ozone": [41],
## "Solar.R": [190],
## "Wind": [7.4],
## "Temp": [67],
## "Month": [5],
## "Day": [1]
## },
## {
## "Ozone": [36],
## "Solar.R": [118],
## "Wind": [8],
## "Temp": [72],
## "Month": [5],
## "Day": [2]
## }
## ]

pretty = TRUE를 설정해주면 JSON 파일 원문에 가깝게 볼 수 있으니 참고하자.

사실 R에서 리스트(list) 데이터 형식을 다루는 것은 초심자에게 쉽지 않은 일이다. 다행이도 jsonlite 패키지의 read_json() 함수와 fromJSON() 함수는 각각 simplifyVectorflatten 인자를 제공하여 중첩된 형식(nested JSON)이나 정형화 되지 않은 임의의 형식(custom format)이 아니라면 간단하게 데이터프레임으로 변환할 수 있으며 예제는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
df_simple = read_json("data/JSON_sample.json", simplifyVector = TRUE)
head(df_simple, 2)
## Ozone Solar.R Wind Temp Month Day
## 1 41 190 7.4 67 5 1
## 2 36 118 8.0 72 5 2

df_flat = fromJSON("data/JSON_sample.json", flatten = TRUE)
head(df_flat, 2)
## Ozone Solar.R Wind Temp Month Day
## 1 41 190 7.4 67 5 1
## 2 36 118 8.0 72 5 2

예제 파일: JSON_sample.json

참고로 JSON형식의 파일로 기록하는 함수는 write_json()이다.

XML

JSON과 같이 웹 데이터 전송에 주로 사용되는 형식인 XML(Extensible Markup Language)은 RESTful API를 이용하거나 웹페이지에서 데이터를 가져올 때(크롤링, crawling) 주로 접하게 된다.

다음 예제는 공공데이터포털의 아파트매매 실거래가 정보 API를 xml 형식으로 받은 자료를 읽어오는 예제이다.

1
2
3
4
5
6
7
library("xml2")
xml = read_xml("data/xml_sample.xml")
xml
## {xml_document}
## <response>
## [1] <header>\n <resultCode>00</resultCode>\n ...
## [2] <body>\n <items>\n <item>\n <거래금액> ...

예제 파일: xml_sample.xml

참고로 xml 데이터의 경우 일반적인 객체 형식이 아니기 때문에 내부 데이터를 잘 다루려면 rvest 패키지를 사용하는 것을 추천한다.

압축 파일

이메일을 보낼 때 여러 파일을 압축해본 경험이 있을 것이다. 이 때 파일의 크기가 줄어들기도 하고 하나로 묶여서 나름 관리하고 공유하기도 편하다. 그런데 굳이 파일을 압축까지 해야할까 싶은 사람이 있겠지만, 텍스트 파일은 압축하면 파일 용량이 제법 많이 줄어든다. 내부 데이터가 복잡하면 압축 효율이 낮은 편인데 그래도 기존 용량의 1/5 정도로 줄어든다. 그리고 데이터가 단순한 패턴 또는 값으로 되어있으면 파일 용량이 1/10 정도 까지 줄어들며 일반적으로 1/8 정도로 줄어드니 특히나 용량이 큰 파일은 되도록이면 압축을 하는 것이 좋다.

그런데 파일을 압축하면 분석할 때 또 다시 압축을 풀어야 하는 번거로움이 있지 않을까?

딱히 그렇지 않다. R에는 압축파일을 처리할 수 있는 기능 또한 존재한다.

※ 단, 압축 파일 내부에는 하나의 텍스트 파일만 있어야 한다.

gzip

보편적으로는 zip 파일로 파일을 압축하지만, 프로그래밍 쪽에서는 zip 보다는 gzip을 더 많이 사용한다고 보면 된다. 압축 파일은 R.utils 패키지의 함수로 다룰수도 있지만 data.table 패키지의 fread() 함수와 fwrite() 함수에서 gzip 파일을 지원한다.

단, 압축파일을 읽으려고 할 경우 다음과 같은 에러가 나니 미리 R.utils 패키지를 설치하는 것을 권장한다.

fread() 함수 에러

1
2
3
4
5
6
library("data.table")
df = fread("data/gzip_sample.gz")
head(df, 2)
## Ozone Solar.R Wind Temp Month Day
## 1: 41 190 7.4 67 5 1
## 2: 36 118 8.0 72 5 2
1
fwrite(df, "fwrite_gzip_write_sample.gz", compress = "gzip")

파일을 읽을 때는 fread() 함수에 별도의 인자를 명시하지 않아도 괜찮지만, fwrite() 함수로 기록할 때는 compress = "gzip"으로 명시해주어야 한다.


<파일 입출력 시리즈>
R 파일 입출력 - 1
R 파일 입출력 - 2
R 파일 입출력 - 3
R 파일 입출력 - 4

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×