R · 기본
데이터로 시작하는 R
데이터 읽고 쓰기 - CSV 와 결측 처리
write.csv·read.csv, 인코딩, colClasses, 결측 찾기·채우기, 이상한 값 점검
개발자KR · 원고 갱신
이 장에서 배우는 것
지금까지는 코드 안에서 벡터와 데이터 프레임을 직접 만들어 썼다. 실제 데이터는 대개 파일로 온다. 편의점 계산대 프로그램이 내보낸 CSV(comma-separated values, 쉼표로 칸을 나눈 텍스트 파일)가 그런 예다. 이 장에서는 CSV 를 R 로 읽고 쓰는 방법과, 읽은 직후 반드시 해야 하는 점검을 다룬다. 점검할 대상은 글자가 깨지지 않았는지, 열의 자료형이 맞는지, 비어 있거나 이상한 값이 없는지다.
write.csv()와read.csv()의 기본 동작과 자주 쓰는 인자를 설명할 수 있다.- 인코딩(encoding, 글자를 바이트로 바꾸는 규칙)이 다를 때
fileEncoding으로 맞출 수 있다. colClasses로 열마다 자료형을 지정해 앞자리 0 이 사라지는 일을 막을 수 있다.na.strings로 "-" 나 빈 칸 같은 결측 표시를NA로 읽고, 결측이 있는 위치를 찾아 근거 있는 값으로 채울 수 있다.- 범위와 관계식으로 이상한 값을 찾고, 지우지 않고 표시하는 습관을 갖는다.
문제 상황
동네 편의점 점주가 3월 첫 주 판매 기록을 CSV 로 넘겨 주었다. 엑셀에서 열어 보면 표는 멀쩡해 보인다. 그런데 R 로 읽어서 합계를 내려 하면 문제가 하나씩 나온다.
- 상품 코드
001이1로 바뀌어 있다. 다른 시스템의 코드와 맞대 볼 수 없다. - 금액 열에 "-" 가 한 칸 들어 있어서 열 전체가 숫자가 아니라 글자로 읽힌다.
- 분류 칸이 비어 있는데 R 은 이것을 결측(
NA)이 아니라 빈 글자로 읽는다. 결측이 몇 개인지 세어 보면 0 이 나온다. - 어떤 커피 행의 수량이 999 다. 금액은 3,000원인데 수량이 999 일 리 없다.
이 문제들은 계산 단계에서 오류 메시지로 드러나지 않는다. 합계가 그럴듯한 숫자로 나오기 때문에 그냥 지나가기 쉽다. 그래서 읽는 순간에 옵션으로 막을 것은 막고, 읽은 뒤에는 점검하는 순서를 몸에 익혀야 한다.
CSV 를 쓰고 읽는 기본
write.csv 와 read.csv
데이터 프레임을 파일로 저장하는 함수는 write.csv(), 파일을 데이터 프레임으로 읽어 오는 함수는 read.csv() 다. 가장 짧은 형태는 다음과 같다.
write.csv(sales, "sales_clean.csv", row.names = FALSE)
back <- read.csv("sales_clean.csv")
write.csv() 는 기본적으로 행 번호를 첫 열로 함께 쓴다. 엑셀로 옮길 때 이 열은 대개 필요 없으므로 row.names = FALSE 를 습관처럼 붙인다. 글자 열은 큰따옴표로 감싸서 쓰고, 결측은 NA 라는 글자로 쓴다. 결측을 빈 칸으로 쓰고 싶으면 na = "" 를 지정한다.
read.csv() 는 첫 줄을 열 이름으로 보고, 나머지 줄을 쉼표 기준으로 나눈 뒤 열마다 자료형을 추측한다. 이 추측이 이 장의 골칫거리다. 열의 모든 값이 정수처럼 보이면 정수로, 하나라도 숫자가 아닌 글자가 섞여 있으면 글자로 읽는다. 사람이 보기에 "금액 열인데 하나가 '-' 일 뿐"이라도 R 은 열 전체를 글자로 본다.
유럽식 CSV 처럼 칸 구분이 세미콜론이고 소수점이 쉼표인 파일은 read.csv2() 가 맞다. 이 책의 예제는 쉼표 구분이라 read.csv() 만 쓴다.
tidyverse 와의 대응
외부 패키지 묶음인 tidyverse 의 readr 도 같은 일을 한다. 이 책은 패키지를 설치하지 않으므로 이름만 알아 두자. 다른 사람의 코드를 읽을 때 도움이 된다.
| 하려는 일 | base R | readr | 메모 |
|---|---|---|---|
| 파일 읽기 | read.csv() | read_csv() | 기본 동작이 서로 다르다 |
| 파일 쓰기 | write.csv(row.names = FALSE) | write_csv() | readr 는 행 번호를 쓰지 않는다 |
| 열 자료형 지정 | colClasses | col_types | 지정 문법이 다르다 |
| 결측 표시 지정 | na.strings | na | 여러 글자를 벡터로 준다 |
| 인코딩 지정 | fileEncoding | locale(encoding = ) | 이름이 다르다 |
인코딩과 colClasses
read.csv 가 파일을 읽는 과정은 세 단계로 나눠 볼 수 있다. 바이트를 글자로 해석하고, 결측을 뜻하는 글자를 NA 로 바꾸고, 열마다 자료형을 정한다. 기본값이 맞지 않으면 각 단계마다 직접 정하는 인자가 있다.
인코딩
컴퓨터는 글자를 그대로 저장하지 못하고 바이트로 바꿔 저장한다. 이 변환 규칙이 인코딩이다. 요즘 macOS 와 Linux 는 대부분 UTF-8 을 쓴다. UTF-8 에서 한글 한 글자는 3바이트다. 반면 오래된 한국어 윈도 프로그램이 내보낸 파일은 CP949(EUC-KR 을 확장한 규칙)인 경우가 많고, 여기서는 한글 한 글자가 2바이트다. 규칙이 다른 파일을 잘못된 규칙으로 읽으면 글자가 깨진다.
파일이 어떤 규칙으로 저장됐는지는 파일 안에 적혀 있지 않다. 그래서 읽는 쪽이 fileEncoding 으로 알려 줘야 한다. 이 장의 코드는 모두 UTF-8 로 쓰고 UTF-8 로 읽는다. 다른 프로그램에서 받은 파일이 깨져 보이면 다음처럼 규칙을 바꿔 다시 읽는다. 이 코드는 예시이고 main.R 에는 들어 있지 않다.
read.csv("from_old_pos.csv", fileEncoding = "CP949")
read.csv("from_excel.csv", fileEncoding = "UTF-8-BOM")
두 번째 줄의 BOM(byte order mark)은 일부 프로그램이 UTF-8 파일 맨 앞에 붙이는 표식이다. 이것을 모르고 읽으면 첫 열 이름 앞에 이상한 글자가 붙는다. "UTF-8-BOM" 으로 지정하면 표식을 걸러 준다.
colClasses
colClasses 는 열마다 읽을 자료형을 글자로 적은 벡터다. 열 이름을 붙여 c(code = "character", qty = "integer") 처럼 쓰면 적은 열만 지정하고 나머지는 자동 추측에 맡길 수 있다. 이 책의 예제에서는 모든 열을 적어서 읽은 결과가 파일 내용에 좌우되지 않게 한다. "Date" 도 지정할 수 있어서 "2025-03-01" 같은 글자를 날짜로 바로 읽는다. 날짜 계산은 뒤에서 문자열과 날짜를 따로 다룬다.
자료형을 미리 지정하면 얻는 것이 둘이다. 하나는 "001" 같은 코드가 숫자로 바뀌지 않는다는 점이다. 다른 하나는 예상과 다른 값이 들어 있을 때 조용히 글자 열이 되지 않고 읽는 시점에 경고가 난다는 점이다. 예를 들어 정수로 지정한 열에 "abc" 가 있으면 R 이 알려 준다.
결측과 이상한 값 점검
결측 찾기
결측은 "값이 있어야 하는데 모른다"는 표시이고 R 에서는 NA 다. 파일에서는 결측이 여러 모양으로 나타난다. 빈 칸, "NA", "-", "?", "없음" 등이다. read.csv() 는 기본적으로 글자 "NA" 와 숫자 열의 빈 칸만 NA 로 바꾼다. 글자 열의 빈 칸은 빈 글자 "" 로 남고, 숫자 열에 섞인 "-" 는 열 전체를 글자로 만든다. 그래서 파일에서 쓰는 결측 표시를 na.strings 로 모두 알려 줘야 한다. 이 규칙은 모든 열에 똑같이 적용된다.
읽은 뒤에는 결측이 어디에 몇 개 있는지 세어 본다. 자주 쓰는 함수는 아래 표와 같다.
| 함수 | 돌려주는 것 | 쓰임 |
|---|---|---|
is.na(x) | 칸마다 TRUE/FALSE | 위치 표시 |
colSums(is.na(df)) | 열별 결측 개수 | 어느 열이 문제인지 파악 |
rowSums(is.na(df)) | 행별 결측 개수 | 결측이 있는 행 찾기 |
complete.cases(df) | 결측이 없는 행이면 TRUE | 온전한 행만 세기 |
결측이 있는 채로 계산하면 결과도 NA 가 된다. 합계에 na.rm = TRUE 를 붙이면 결측을 빼고 계산한 값이 나오지만, 그 값은 "모르는 것을 0 으로 친 합계"와 같아서 실제보다 작다. 이 옵션은 결측의 의미를 정한 뒤에 쓴다.
결측 여부를 물을 때 x == NA 로 쓰면 안 된다. "모르는 값과 같은가"의 답은 항상 모른다이므로 결과가 NA 다. 반드시 is.na(x) 를 쓴다.
결측 채우기
채우기는 값을 지어내는 일이 아니라 근거를 찾아 복원하는 일이어야 한다. 이 장의 예제에서 쓰는 근거는 두 가지다.
- 같은 상품의 다른 행에 적힌 분류를 찾아 빈 분류를 채운다. 조회표를 만들고
match()로 찾는다. - 가격표에서 상품별 단가를 가져와
금액 = 수량 × 단가관계로 빠진 수량이나 금액을 복원한다. 앞 장에서 다룬 벡터화 덕분에 반복문 없이 한 줄로 끝난다.
근거가 없는데 평균이나 0 으로 채우는 것은 마지막 수단이다. 평균으로 채운 값은 실제로 일어난 판매가 아니므로 이후 통계를 왜곡한다. 채웠다면 어느 행을 어떤 근거로 채웠는지 출력으로 남긴다.
이상한 값 점검
이상한 값은 NA 가 아니어서 R 이 알려 주지 않는다. 사람이 규칙을 정해 찾아야 한다. 규칙의 종류는 크게 둘이다.
- 범위 규칙: 편의점 한 번의 판매 수량이 100개를 넘을 수 있는가.
range()로 최솟값과 최댓값을 보면 대부분 바로 드러난다. - 관계 규칙: 열끼리 성립해야 하는 식이 있는가. 금액은 수량에 단가를 곱한 값이어야 한다.
음수 수량은 입력 실수일 수도 있고 반품일 수도 있다. 이런 값은 지우지 말고 표시하는 열을 따로 만든다. 실제로 반품인지는 점주에게 확인할 일이다. 값을 고칠 때는 수량 999 처럼 다른 열과 모순되는 경우에 한해 다른 열에서 근거를 찾는다.
완성 코드
파일 하나(main.R)다. 원본 CSV 를 코드가 직접 쓰고, 읽고, 점검하고, 정리한 결과를 다시 쓴다. 임시 폴더를 쓰므로 실행해도 작업 폴더에 파일이 남지 않는다.
main.R
# main.R - 편의점 판매 기록 CSV 를 읽고 쓰며 결측과 이상한 값을 점검한다
say <- function(...) writeLines(paste0(...))
show_types <- function(d) {
writeLines(paste0(names(d), ": ", vapply(d, function(v) class(v)[1], character(1))))
}
na_report <- function(d) {
n <- colSums(is.na(d))
writeLines(sprintf("%-8s %d", names(n), n))
}
# 글자 수와 바이트 수는 다르다
say("삼각김밥 글자 수: ", nchar("삼각김밥"))
say("삼각김밥 바이트 수: ", nchar("삼각김밥", type = "bytes"))
# 1. 흠이 있는 원본 CSV 를 코드로 쓴다
raw_path <- file.path(tempdir(), "sales_raw.csv")
raw_lines <- c(
"date,code,item,category,qty,amount",
"2025-03-01,001,삼각김밥,식품,3,4500",
"2025-03-01,002,생수,음료,2,2000",
"2025-03-02,003,라면,식품,,3600",
"2025-03-02,002,생수,음료,4,4000",
"2025-03-03,004,커피,음료,2,-",
"2025-03-03,001,삼각김밥,식품,5,7500",
"2025-03-04,005,건전지,잡화,1,3000",
"2025-03-04,003,라면,식품,-1,-1200",
"2025-03-05,002,생수,,3,3000",
"2025-03-05,004,커피,음료,999,3000",
"2025-03-05,004,커피,음료,1,1500"
)
con <- file(raw_path, open = "w", encoding = "UTF-8")
writeLines(raw_lines, con)
close(con)
say("원본 파일: ", basename(raw_path))
say("")
# 2. 기본값으로 읽으면
naive <- read.csv(raw_path, fileEncoding = "UTF-8")
say("[기본값으로 읽기]")
show_types(naive)
say("code 첫 값: ", naive$code[1])
na_report(naive)
say("")
# 3. 자료형과 결측 표시를 지정해 읽으면
types <- c(date = "Date", code = "character", item = "character",
category = "character", qty = "integer", amount = "integer")
sales <- read.csv(raw_path, colClasses = types,
na.strings = c("", "NA", "-"), fileEncoding = "UTF-8")
say("[colClasses 와 na.strings 를 지정해 읽기]")
show_types(sales)
say("code 첫 값: ", sales$code[1])
na_report(sales)
rows <- which(rowSums(is.na(sales)) > 0)
say("결측이 있는 행: ", paste(rows, collapse = ", "))
say("amount 합계(na.rm 없이): ", sum(sales$amount))
say("amount 합계(na.rm = TRUE): ", sum(sales$amount, na.rm = TRUE))
say("")
# 4. 점검하고 채운다
say("[점검과 채우기]")
price <- c("삼각김밥" = 1500, "생수" = 1000, "라면" = 1200,
"커피" = 1500, "건전지" = 3000)
unit <- unname(price[sales$item])
pair <- c("item", "category")
known <- unique(sales[complete.cases(sales[pair]), pair])
miss <- is.na(sales$category)
sales$category[miss] <- known$category[match(sales$item[miss], known$item)]
say("채운 분류: ", sales$item[miss], " -> ", sales$category[miss])
miss <- is.na(sales$qty)
sales$qty[miss] <- as.integer(sales$amount[miss] / unit[miss])
say("채운 수량: 행 ", which(miss), " = ", sales$qty[miss])
say("수량 범위: ", paste(range(sales$qty), collapse = " ~ "))
mismatch <- which(abs(sales$amount - sales$qty * unit) > 0)
say("금액이 맞지 않는 행: ", paste(mismatch, collapse = ", "))
sales$qty[mismatch] <- as.integer(sales$amount[mismatch] / unit[mismatch])
say("고친 수량: 행 ", mismatch, " = ", sales$qty[mismatch])
miss <- is.na(sales$amount)
sales$amount[miss] <- as.integer(sales$qty[miss] * unit[miss])
say("채운 금액: 행 ", which(miss), " = ", sales$amount[miss])
sales$is_return <- sales$qty < 0
say("반품 행: ", paste(which(sales$is_return), collapse = ", "))
say("남은 결측 수: ", sum(is.na(sales)))
say("정리 후 금액 합계: ", sum(sales$amount))
say("")
# 5. 정리본을 저장하고 다시 읽어 확인한다
say("[저장과 다시 읽기]")
clean_path <- file.path(tempdir(), "sales_clean.csv")
write.csv(sales, clean_path, row.names = FALSE, fileEncoding = "UTF-8")
say("정리본 파일: ", basename(clean_path))
say("헤더: ", readLines(clean_path, n = 1))
say("줄 수: ", length(readLines(clean_path)))
back <- read.csv(clean_path, colClasses = c(types, is_return = "logical"),
fileEncoding = "UTF-8")
say("다시 읽은 값이 같은가: ", isTRUE(all.equal(sales, back)))
줄별 해설
say,show_types,na_report: 출력을 짧게 쓰기 위한 도우미 함수다. 데이터 프레임을 그대로 출력하면 한글 칸 너비 때문에 정렬이 환경마다 달라질 수 있어서, 필요한 값만 글자로 만들어writeLines()로 낸다.nchar(..., type = "bytes"): 글자 수 4 와 바이트 수 12 를 비교해 UTF-8 에서 한글이 3바이트임을 확인한다. 이 차이 때문에 인코딩을 맞춰야 한다.raw_lines: 일부러 흠을 넣은 CSV 본문이다. 3번째 데이터 줄은 수량이 비었고, 5번째는 금액이 "-" 이고, 9번째는 분류가 비었다. 8번째는 반품처럼 보이는 음수이고, 10번째는 수량이 999 다.file(..., encoding = "UTF-8")와writeLines(): 파일 연결을 UTF-8 로 열어 한 줄씩 쓴다. 쓰고 나면close()로 닫아야 내용이 확실히 저장된다.naive: 옵션 없이 읽은 결과다.code는 정수 1 이 되고,amount는 "-" 때문에 글자 열이 되고, 빈 분류는NA가 아니라서 결측 개수에서 빠진다.types: 열 이름을 붙인colClasses벡터다. 같은 벡터를 마지막에 다시 읽을 때도 재사용한다.na.strings = c("", "NA", "-"): 빈 칸, "NA", "-" 를 모두 결측으로 읽는다. 그래서category,qty,amount에 결측이 하나씩 잡힌다.which(rowSums(is.na(sales)) > 0): 행마다 결측 개수를 더해 0 보다 큰 행 번호를 찾는다.sum(sales$amount)와na.rm = TRUE: 결측이 하나라도 있으면 합계가NA이고, 결측을 빼면 결측 행 금액만큼 작은 값이 나온다.price와unit: 가격표를 이름 붙은 벡터로 만들고, 상품명으로 찾아 행마다 단가를 뽑는다.unname()은 이름 표를 떼어 계산 결과가 깔끔하게 남게 한다.known: 분류가 있는 행의 (상품, 분류) 쌍을 중복 없이 모은 조회표다.match()는 각 상품이 조회표의 몇 번째에 있는지 알려 주고, 그 위치로 분류를 가져온다.as.integer(sales$amount[miss] / unit[miss]): 금액을 단가로 나눠 수량을 복원한다. 정수 열에 실수를 넣으면 열 전체가 실수로 바뀌므로as.integer()로 감싼다.abs(sales$amount - sales$qty * unit) > 0: 금액이 수량 곱하기 단가와 다른 행을 찾는다. 결측이 낀 행은 비교 결과가NA이고which()는NA를 건너뛰므로 결측 행은 여기서 걸리지 않는다. 그래서 결측을 먼저 채우거나 따로 다뤄야 한다.sales$is_return: 음수 수량을 지우지 않고 표시만 하는 논리 열이다.write.csv(..., row.names = FALSE, fileEncoding = "UTF-8"): 행 번호 열 없이 UTF-8 로 저장한다. 저장한 파일의 첫 줄과 줄 수를 확인하고, 같은types로 다시 읽어all.equal()로 원본과 같은지 확인한다. 데이터 프레임 전체를 비교할 때는isTRUE()로 감싸 참 거짓만 얻는다.
실행 결과
$ Rscript main.R
삼각김밥 글자 수: 4
삼각김밥 바이트 수: 12
원본 파일: sales_raw.csv
[기본값으로 읽기]
date: character
code: integer
item: character
category: character
qty: integer
amount: character
code 첫 값: 1
date 0
code 0
item 0
category 0
qty 1
amount 0
[colClasses 와 na.strings 를 지정해 읽기]
date: Date
code: character
item: character
category: character
qty: integer
amount: integer
code 첫 값: 001
date 0
code 0
item 0
category 1
qty 1
amount 1
결측이 있는 행: 3, 5, 9
amount 합계(na.rm 없이): NA
amount 합계(na.rm = TRUE): 30900
[점검과 채우기]
채운 분류: 생수 -> 음료
채운 수량: 행 3 = 3
수량 범위: -1 ~ 999
금액이 맞지 않는 행: 10
고친 수량: 행 10 = 2
채운 금액: 행 5 = 3000
반품 행: 8
남은 결측 수: 0
정리 후 금액 합계: 33900
[저장과 다시 읽기]
정리본 파일: sales_clean.csv
헤더: "date","code","item","category","qty","amount","is_return"
줄 수: 12
다시 읽은 값이 같은가: TRUE
기본값으로 읽으면 결측이 1개로 보이지만 실제로는 3개였다. 합계도 결측 때문에 30,900원으로 3,000원 모자라게 나오고, 정리 후에는 33,900원이 된다.
실무에서 자주 틀리는 것
행 번호 열이 파일에 따라붙는다
틀린 코드다.
write.csv(sales, "out.csv")
back <- read.csv("out.csv")
names(back) # 첫 열 이름이 X 로 붙는다
write.csv() 가 행 번호를 첫 열로 쓰고, 읽을 때 그 열은 이름이 없어 X 라는 이름을 받는다. 저장할 때 끈다.
write.csv(sales, "out.csv", row.names = FALSE)
상품 코드의 앞자리 0 이 사라진다
틀린 코드다.
d <- read.csv("sales_raw.csv")
d$code[1] # 1
"001" 이 정수로 추측되어 1 이 된다. 이렇게 바뀐 뒤에는 원래 코드를 복원할 수 없다. 코드나 전화번호처럼 계산하지 않는 숫자는 글자로 읽는다.
d <- read.csv("sales_raw.csv", colClasses = c(code = "character"))
d$code[1] # "001"
결측을 == 로 찾는다
틀린 코드다.
qty <- c(2, NA, 5)
which(qty == NA) # integer(0), 찾지 못한다
qty == NA 의 결과는 세 칸 모두 NA 라서 which() 가 아무것도 고르지 못한다. 오류도 경고도 없으니 "결측이 없다"고 착각한다. 고친 코드다.
which(is.na(qty)) # 2
결측 표시를 모르고 읽어서 열이 글자가 된다
틀린 코드다.
d <- read.csv("sales_raw.csv")
sum(d$amount) # 오류: 'type' (character) 이 잘못됨
"-" 하나 때문에 amount 가 글자 열이 되어 합계가 오류가 난다. as.numeric() 으로 억지로 바꾸면 경고와 함께 결측이 생겨 원인을 숨긴다. 읽을 때 결측 표시를 알려 주는 편이 낫다.
d <- read.csv("sales_raw.csv", na.strings = c("", "NA", "-"))
sum(d$amount, na.rm = TRUE)
한눈에 보기
| 하려는 일 | 코드 | 결과 | 주의 |
|---|---|---|---|
| 저장 | write.csv(d, path, row.names = FALSE) | 행 번호 없는 CSV | 결측은 기본이 "NA", na = "" 로 바꾼다 |
| 글자 규칙 맞추기 | read.csv(path, fileEncoding = "UTF-8") | 깨지지 않은 글자 | 옛 파일은 "CP949" 를 시도한다 |
| 자료형 고정 | colClasses = c(code = "character") | 앞자리 0 보존 | 적지 않은 열은 자동 추측 |
| 결측 표시 알리기 | na.strings = c("", "NA", "-") | 해당 칸이 NA | 모든 열에 적용된다 |
| 결측 세기 | colSums(is.na(d)) | 열별 개수 | == NA 는 쓰지 않는다 |
| 이상한 값 찾기 | range(), 관계식 비교 | 의심 행 번호 | 지우지 말고 표시하고 근거를 남긴다 |
연습 문제
- 데이터 프레임
d를write.csv(d, "d.csv")로 저장한 뒤read.csv("d.csv")로 읽었더니 열이 하나 늘어 있었다. 늘어난 열의 이름과 원인을 쓰고, 늘지 않게 저장하는 코드를 쓰시오. - 어떤 파일에서 결측이 "없음" 과 "?" 로 적혀 있고, 상품 코드 열
code에 "0012" 처럼 앞자리 0 이 있다. 파일 이름이gs.csv, 인코딩이 UTF-8 일 때 이 조건으로 읽는 코드를 쓰시오. qty <- c(2, NA, 5, 999, -1)일 때 다음 세 식의 결과를 쓰시오.sum(qty, na.rm = TRUE),qty > 100,which(qty > 100).x <- c(4, NA, 6, NA, 8)의 결측을 나머지 값의 평균으로 채우는 코드를 쓰고 결과를 쓰시오. 이 방법을 조심해야 하는 이유도 한 문장으로 쓰시오.
정답과 해설
- 열 이름은
X다.write.csv()는 기본으로 행 번호를 첫 열에 쓰고, 그 열에는 이름이 없어서 읽을 때X라는 이름이 붙는다. 저장은write.csv(d, "d.csv", row.names = FALSE)로 한다. - 다음과 같이 쓴다.
파일에 빈 칸도 결측으로 볼 거라면read.csv("gs.csv", colClasses = c(code = "character"), na.strings = c("없음", "?"), fileEncoding = "UTF-8")na.strings에""를 더한다. 문제에는 그런 조건이 없어서 넣지 않았다. sum(qty, na.rm = TRUE)는 2 + 5 + 999 - 1 = 1005 다.qty > 100은FALSE NA FALSE TRUE FALSE이고, 결측 칸은 비교 결과도NA다.which(qty > 100)은NA를 건너뛰고 TRUE 인 위치만 돌려주므로 4 다.x[is.na(x)] <- mean(x, na.rm = TRUE)를 실행하면 평균이 (4 + 6 + 8) / 3 = 6 이므로x는4 6 6 6 8이 된다. 실제로 일어난 값이 아닌 6 이 늘어나 값들이 평균 근처에 몰려 보이므로 이후 통계가 실제보다 안정적으로 나온다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.