R) 파일 입출력 - 4

R) 파일 입출력 - 4

파일을 다루다 보면 다양한 상황에 직면한다.

왜 나는? 왜 나만!!! 안되는 것일까 ㅠㅠ

이번 포스팅에서는 파일을 읽을 때 발생하는 다양한 문제를 다뤄보고자 한다.

파일 경로

실습과 함께 하고자 한다면 다음 파일을 다운로드 하자

예제 파일: download.zip

초보적인 실수이지만 숙련자도 자주 겪는 실수이다. 숙련자는 바로바로 현재 R 환경의 작업폴더를 확인하고 조치를 하지만 초보자는 당황하기 마련이다. 파일 경로 부분에서 실수하는 사람은 다음과 같은 특징을 가지고 있진 않은지 되돌아 보도록 하자.

  • 파일을 보통 바탕화면에 다운로드
  • 파일이 어디에 다운로드 되었는지 잘 모름
  • 파일 찾을 때 정확한 경로가 아닌 검색을 통해서 찾음
  • 바탕화면에 각종 파일과 폴더가 50% 이상 차지

여기에서 나열한 사항을 전부 만족한다면 R이 문제가 아니라 컴퓨터 자체를 사용하는데 익숙하지 않은 상태이니 얼른 컴퓨터에 익숙해지도록 하자. 아무튼 차근차근 다음 내용을 알아보자.

현재 R과 연결된 폴더(작업폴더) 경로를 알아보려면 getwd() 함수를 사용해야 한다. ?getwd를 입력하여 도움말을 호출하면 Get or Set Working Directory 라고 나오지만 getwd() 함수의 경우 GET Working Directory address 라고 이해하는 것이 좀 더 낫지 않을까 한다.

getwd()를 사용하여 현재 작업폴더를 확인했다면 해당 위치에 파일을 옮겨놓도록 하자. 하지만 또 다시 파일을 읽지 못하는 사태가 발생할 수 있다. 보통 이런 경우에 에러가 발생한다고 하는 사람은 분명히 해당 폴더에 옮겼다고 한다.

1
2
df = read.csv("data.csv")
## Warning in file(file, "rt"): 파일 'data.csv'를 여는데 실패했습니다: No such file or directory

네… 뭐 그러시겠죠.

정확하게 getwd() 함수의 출력 결과로 나오는 폴더에 옮겼을까? 그건 또 다른 문제이다. 이 상황에서 list.files() 함수를 입력하고 결과를 보도록 하자.

1
2
3
setwd("C:/Users/Encaion/Documents/data")
list.files()
## [1] "download"

data.csv파일이 보이는데도 파일을 읽지 못했다면 오타일 가능성이 매우 높고, 파일이 보이지 않는다면 특정 폴더 아래에 있을 수 있다. 특히 압축파일을 다운로드 받은 후 압축파일을 풀게 되면 보통 기본 설정이 해당 압축파일명으로 하위 폴더를 생성하고 그 밑에 압축파일의 내용을 위치시킨다. 이 상황을 재현한 것이 위의 코드이다.

다음과 같이 작성해보자.

1
2
3
setwd("C:/Users/Encaion/Documents/data")
list.files(path = "download")
## [1] "data.csv"

list.files() 함수는 path 인자에 값을 할당하지 않으면 작업폴더의 파일/폴더 목록을 보여주는데 여기에 하위 폴더 “download”를 명시해주어 해당 폴더 아래의 파일/폴더 목록을 출력해준 것이다. 해당 코드로 “data.csv” 파일을 확인했다면 다음과 같이 입력할지도 모르다.

1
2
df = read.csv("data.csv")
## Warning in file(file, "rt"): 파일 'data.csv'를 여는데 실패했습니다: No such file or directory

왜 같은 실수를 반복하는 것일까?

제대로 하려면 다음과 같이 입력해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
setwd("C:/Users/Encaion/Documents/data")
df = read.csv("download/data.csv")
head(df, 2)
## CST Max.TemperatureF Mean.TemperatureF Min.TemperatureF Max.Dew.PointF
## 1 2016-1-1 37 24 11 19
## 2 2016-1-2 41 23 5 22
## MeanDew.PointF Min.DewpointF Max.Humidity Mean.Humidity Min.Humidity
## 1 13 8 88 68 47
## 2 14 4 100 72 44
## Max.Sea.Level.PressureIn Mean.Sea.Level.PressureIn Min.Sea.Level.PressureIn
## 1 30.50 30.39 30.30
## 2 30.35 30.30 30.22
## Max.VisibilityMiles Mean.VisibilityMiles Min.VisibilityMiles
## 1 10 10 10
## 2 10 10 10
## Max.Wind.SpeedMPH Mean.Wind.SpeedMPH Max.Gust.SpeedMPH PrecipitationIn
## 1 20 9 23 0
## 2 15 6 18 0
## CloudCover Events WindDirDegrees
## 1 0 280
## 2 0 312

앞에서 소개한 list.files() 함수의 중요 인자 설명을 다음에 기술하니 참고하도록 하자.

  • path: 파일/폴더 목록을 읽어올 하위 폴더 지정
  • pattern: 파일/폴더 목록 필터링에 사용할 조건(정규표현식 가능)
  • full.names: TRUE 작업폴더 다음의 모든 경로 출력
  • recursive: TRUE 폴더 안의 폴더에 있는 모든 파일/폴더 목록 전부 탐색 및 출력
  • include.dirs: FALSE 출력물에서 폴더 경로 제외

인코딩

이전 포스팅에서 다음과 같이 기술하였다.

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

혹시나 데이터베이스에서 파일을 읽어오는 경우 앞에서 소개한 인코딩을 아무리 해봐도 먹히지 않는 경우가 있다. 보통 DB(RDB, 관계형 데이터베이스)의 경우 인코딩이 latin-1로 지정되어있어 특수문자/숫자/영어를 제외한 대부분의 문자가 인코딩의 영향을 받아 제대로된 결과를 얻기 힘들다. 이 때 다음의 쿼리를 실행해보자.

1
set names 'utf8';

인코딩을 “UTF-8”로 임시 설정하고 사용하는 것이니 이 쿼리를 RMySQL 패키지나 RMariaDB 패키지를 사용하는 경우 dbSendQuery()를 사용하면 된다.

데이터베이스 연결 이외에도 필자가 겪은 굉장히 특이한 경험이 있었는데, 개인 레슨을 받으시는 분이 작성한 파일을 읽어오려고 하는데 인코딩을 아무리 설정해줘도 안되는 것이었다. 시도한 입력값은 다음과 같다.

CP949/cp949/euckr/euc-kr/utf8/utf-8/UTF-8

알고보니 “CP-1252”(기억이 가물가물)라는 인코딩이었던 것으로 기억한다. 도대체 왜 설정이 이렇게 되어있나 궁금해서 노트북 구입처를 여쭤봤더니 미국 유학 중에 구매한 것이라고 했다. “CP-949”는 한국어를 위한 인코딩이라 다른 언어가 기본인 윈도우는(정확하게는 시스템 locale 설정이 되겠다) 좀 생소한 인코딩으로 작성되어있을 수 있으니 정 안되면 이 부분을 확인하도록 하자.

특수한 텍스트 파일

사태 파악

파일을 읽을 수 없는 것 만큼 답답한 일도 없다. 이 글을 읽기 까지 다양한 방법을 시도해봤겠지만 용량이 작은 파일의 경우 메모장이나 엑셀로 열어서 이미 눈으로 살펴본 이후일것이다. 하지만 Windows 운영체제 같이 GUI 환경이 아니거나 파일이 너무 커서 열어볼 수 없는 경우에는 딱히 방법이 없다. 이 때 사용할 수 있는 함수가 readLines() 함수이다.

1
2
3
4
readLines("data/covid19_200330_problem.csv", n = 3)
## [1] "FIPS,Admin2,Province_State,Country_Region,Last_Update,Lat,Long_,Confirmed,Deaths,Recovered,Active,Combined_Key"
## [2] "45001,Abbeville,South,Carolina,US,2020-03-30,22:52:45,34.22333378,-82.46170658,3,0,0,0,\"Abbeville, South Carolina, US\""
## [3] "22001,Acadia,Louisiana,,US,2020-03-30,22:52:45,30.295064899999996,-92.41419698,11,1,0,0,\"Acadia, Louisiana, US\""

예제 파일: covid19_200330_problem.csv

이 함수는 쉼표 같은 구분자를 처리하지 않고 오직 줄바꿈만 처리한다. 그래서 csv파일이 아닌 경우 대상 파일의 구분자나 구조를 모를 때 최초 몇 줄만 읽어와서 파일을 파악해볼 수 있다. 앞의 코드에서 사용한 n 인자를 활용해보자.

header가 없는 경우

변수 제목이 없는 경우다. 다음을 확인해보자.

1
2
3
4
5
6
7
8
9
df = read.csv("data/covid19_200330_no_header.csv")
head(df)
## X45001 Abbeville South.Carolina US X2020.03.30.22.52.45
## 1 22001 Acadia Louisiana US 2020-03-30 22:52:45
## 2 51001 Accomack Virginia US 2020-03-30 22:52:45
## 3 16001 Ada Idaho US 2020-03-30 22:52:45
## 4 19001 Adair Iowa US 2020-03-30 22:52:45
## 5 21001 Adair Kentucky US 2020-03-30 22:52:45
## 6 29001 Adair Missouri US 2020-03-30 22:52:45

예제 파일: covid19_200330_no_header.csv

변수명에 원 자료의 데이터가 들어있다. 변수명에 강제 할당된 데이터를 다시 꺼내오고 재처리 하는 일을 굉장히 귀찮다. 왜냐햐면 R의 변수명은 숫자로 시작하는 것을 허용하지도 않고, 사용할 수 있는 특수문자도 제한적이다. 그래서 여기에서는 다음과 같이 입력하는 것을 권장한다.

1
2
3
4
5
6
7
8
9
df = read.csv("data/covid19_200330_no_header.csv", header = FALSE)
head(df)
## V1 V2 V3 V4 V5
## 1 45001 Abbeville South Carolina US 2020-03-30 22:52:45
## 2 22001 Acadia Louisiana US 2020-03-30 22:52:45
## 3 51001 Accomack Virginia US 2020-03-30 22:52:45
## 4 16001 Ada Idaho US 2020-03-30 22:52:45
## 5 19001 Adair Iowa US 2020-03-30 22:52:45
## 6 21001 Adair Kentucky US 2020-03-30 22:52:45

예제 파일: covid19_200330_no_header.csv

자동으로 변수명을 V1 ~ V5 이렇게 지정해주었다. 필요시 colnames() 함수를 사용하여 변수명을 지정하면 되니 참고하도록 하자.

별도 텍스트 추가

어떤 파일은 파일의 윗 부분에 별도의 정보가 기록되어 있다. 그냥 읽으면 다음과 같이 나온다.

1
2
3
4
5
6
7
8
9
df = read.csv("data/sample_add_new_lines.txt")
head(df)
## Mydata.
## 1 DataDoctor's blog
## 2 https://datadoctorblog.com
## 3 AWS_ID#TM#TA#Wind#X.
## 4 108#2016-07-01 00#24.2#2.3#=
## 5 108#2016-07-01 01#24.3#2.3#=
## 6 108#2016-07-01 02#23.7#3.8#=

예제 파일: sample_add_new_lines.txt

에러가 날법도 한데 그대로 읽혀지긴 했다. 하지만 데이터는 세 번째 줄 부터 시작되는 것 처럼 보인다. 이제 다음과 같이 입력해보자.

1
2
3
4
5
6
7
8
9
df = read.csv("data/sample_add_new_lines.txt", sep = "#", skip = 3)
head(df)
## 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 =
## 4 108 2016-07-01 03 23.3 3.0 =
## 5 108 2016-07-01 04 23.5 2.1 =
## 6 108 2016-07-01 05 23.5 2.7 =

예제 파일: sample_add_new_lines.txt

skip 인자는 데이터의 최초 몇 줄을 무시하고 다음부터 데이터를 읽을지 관여하는 인자이다. 그렇기에 초반부에 불필요한 정보가 있다면 이 인자를 활용하는 것도 방법이다.

구분자 문제

데이터 분석을 하게 되면 csv 파일을 많이 다루게 되는데 가끔 읽고 쓰는데 문제가 생긴다. 분명 이전에는 잘 다뤘었는데 중간 파일을 저장하고 다시 읽으려고 하니 일부가 잘려있다던지 하는 문제가 생길 수 있다. 이는 보통 예기치 못한 특수문자 또는 쉼표 문제이다. 예를 들어 가격 정보에 10,500원 라고 기록되어있다던지, 게시글이나 뉴스기사같은 텍스트에 있는 쉼표가 문제가 될 수 있다.

read.csv() 같이 파일을 읽는 함수는 읽고자 하는 파일의 두 번째 줄에(첫 번째 줄은 보통 변수명) 쉼표가 많이 있는 경우 다음과 같은 에러가 발생한다.

1
2
df = read.csv("data/covid19_200330_problem.csv")
## Error in read.table(file = file, header = header, sep = sep, quote = quote, : more columns than column names

예제 파일: covid19_200330_problem.csv

이럴 때에는 변수명을 제외하고 읽어보는 것이 좋으며 header인자를 조작해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
df = read.csv("data/covid19_200330_problem.csv", header = FALSE)
head(df)
## V1 V2 V3 V4 V5 V6
## 1 FIPS Admin2 Province_State Country_Region Last_Update Lat
## 2 45001 Abbeville South Carolina US 2020-03-30
## 3 22001 Acadia Louisiana US 2020-03-30
## 4 47153 Sequatchie Tennessee US 2020-03-30
## 5 40135 Sequoyah Oklahoma US 2020-03-30
## 6 25027 Worcester Massachusetts US 2020-03-30
## V7 V8 V9 V10 V11 V12 V13
## 1 Long_ Confirmed Deaths Recovered Active Combined_Key NA
## 2 22:52:45 34.22333378 -82.46170658 3 0 0 0
## 3 22:52:45 30.295064899999996 -92.41419698 11 1 0 0
## 4 22:52:45 35.36979721 -85.41022136 0 0 0 0
## 5 22:52:45 35.49447108 -94.75463785 2 1 0 0
## 6 22:52:45 42.35026951 -71.90493363 390 5 0 0
## V14
## 1
## 2 Abbeville, South Carolina, US
## 3 Acadia, Louisiana, US
## 4 Sequatchie, Tennessee, US
## 5 Sequoyah, Oklahoma, US
## 6 Worcester, Massachusetts, US

예제 파일: covid19_200330_problem.csv

비슷한 종류의 오류로는 write.csv() 같은 파일 저장 함수로 데이터를 저장하면서 row.names = FALSE를 선언하지 않은 경우 rowname까지 같이 저장되는 경우도 비슷한 문제를 야기하기도 한다.

앞에서 소개한 문제가 제법 빈번하게 나타나기에 readr 또는 data.table 패키지의 read_csv() 또는 fread() 같이 파일 읽는 함수는 구분자 문제 또는 변수 개수오류 부분을 비교적 잘 처리해주는 편이다.

파일을 기록할 때 발생하는 문제

예를 들어서 다음과 같은 코드로 내장 데이터 iris를 저장했다고 하자.

1
write.csv(iris, "iris.csv")

보통 파일이 제대로 기록되었는지 확인하기 위해서 위와 같이 코드를 입력한 다음에 엑셀을 사용하여 해당파일을 열어보기 마련이다.

엑셀 파일로 열어본 iris.csv

그 후에 뭔가 조작을 더 하고 이전처럼 똑같은 코드로 기존 파일을 덮어쓰려고 하면 에러가 발생한다.

1
write.csv(iris, "iris.csv")

Error in file(file, ifelse(append, "a", "w")) : 커넥션을 열 수 없습니다
추가정보: 경고메시지(들):
In file(file, ifelse(append, "a", "w")) :
파일 'iris.csv'를 여는데 실패했습니다: Permission denied

Linux 운영체제라면 권한(Permission) 문제가 빈번하게 발생하지만 Windows운영체제는 권한과 관련된 문제가 발생하는 일이 드물다. 그래서 무언가를 저장할 때 에러메세지에 Permission 이라는 단어가 있으면 딱 이 문제일 가능성이 크다. 이럴 때는 해당 파일을 열고 있는 프로그램을 종료하고 다시 코드를 실행하면 정상동작할 것이다.

이 에러는 특히 5~10개 파일을 동시에 마구잡이로 열어두면서 정작 파일 살펴볼 때는 너무 많고 정신없어서 원하는 파일이 어떤 프로그램에서 열려있는지 찾는데 수 분을 허비하는 동시에 왜 내 노트북 배터리는 이렇게 빨리닳는지 고민하는 사람들이 자주 하는 실수이다.


<파일 입출력 시리즈>
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

×