R 4.3.1

QGIS 3.32.1

 

하다 하다 이제 QGIS까지 공부하게 되었다. 내가 해야 되는 일이 서울시 특정 구역 안에 있는 사업체들의 현황을 조사하는 일이었다.

지도에 빨갛게 표시되어 있는 구역이다. 종로 1, 2, 3, 4가동, 을지로동, 광희동, 필동(행정동 기준)이 일부씩 차지하고 있는 곳이라서 행정구역 단위로 쪼갤 수 없다. 우리 회사에는 2020년 기준 서울 소재 약 120만 개의 사업체들에 대한 위치 좌표 자료가 있다. QGIS를 이용해서 이 구역 내에 위치한 사업체를 따로 뽑아 테이블을 만들 것이다. 먼저 R에서 원자료를 읽어서 필요한 변수만 추출하고 CSV 파일로 저장한다.

 

sbs20co1 <- read.table("../data/SBS/2020/coord/사업체조사자료2020_서울_basexy.txt", sep="|", header=T, fileEncoding="CP949")
sbs20co2 <- sbs20co1[,c("ad_cd", "rprs_sd_cd", "rprs_gnr", "org_form_cd", "estm_div_cd", "mbz_indst_div_cd_10th", "surv_phs_woke_sum", "x_code", "y_code")]
names(sbs20co2) <- c("add2", "gender", "age", "form", "div", "ind5", "emp", "x_code", "y_code")
write.csv(sbs20co2, file="sbs20co2.csv")

 

행정구역(add2, 행정동까지), 대표자성별(gender), 대표자연령(age), 조직형태(form, 개인, 법인), 사업체구분(div, 본사, 지사 등), 표준산업분류(ind5), 종사자수(emp), 그리고 위치좌표(x_code, y_code)가 있다.

 

국가공간정보포털 > 오픈 마켓 (http://data.nsdi.go.kr/dataset)에 들어가서 '행정동'으로 검색하면 밑에 '(센서스경계) 행정동경계'가 나오는데 이게 행정동이다. 다운 받고(회원가입, 로그인 필요) 압축 푼 뒤에 QGIS에서 데이터 원본 관리자 열기 아이콘을 클릭한다.

 

 

창이 뜨면 왼쪽 메뉴에서 '벡터'를 선택하고, 오른쪽 '원본'에서 방금 압축 푼 폴더에서 .shp 파일을 불러온 다음 추가(A)를 누르면 화면에 지도가 뜰 것이다.

 

 

지도를 보면 전국이 다 뜨는데, 일단 내가 필요한 것은 서울이기 때문에 이걸 이용해 서울만 따로 추출한 행정동 경계 지도를 만든다. QGIS 화면 왼쪽 하단에 레이어 창이 있는데 방금 불러온 레이어 이름을 우클릭하고 '속성 테이블 열기(A)'를 선택하면 다음과 같은 테이블이 보일 것이다.

 

 

테이블을 계속 스크롤 해서 내려가면 감으로 알게 되는데, ADM_CD 값이 '11'로 시작하면 서울이란 뜻이다. 이제 ADM_CD 가 11로 시작하는 객체만 선택해야 한다. 위에 그림에 빨간 동그라미가 '표현식을 이용해  객체 선택'이란 건데 클릭하면 창이 하나 뜨고 거기에 표현식을 아래와 같이 쓰고(그냥 외워) 객체 선택을 누르면 서울의 행정동만 선택이 된다.

지도 화면을 잘 보면 서울만 노란색으로 변해 있는 걸 볼 수 있는데, 확대 버튼 누르고 서울 주변에 창을 그려서 서울만 확대 해서 보면 더 잘 볼 수 있다.

 

 

레이어 창에서 레이어 이름 우클릭 > Export > 선택한 객체를 다른 이름으로 저장 클릭하면 서울 행정동만 따로 저장이 되고 현재 화면에 새 레이어도 생성된다. 이제 원래 레이어는 삭제하고 새로 생성된 레이어만 남긴다.

 

다음으로, 도로 지도를 불러야 되는데,QGIS에서 플러그인(P) > 플러그인 관리 및 설치 들어간 다음에 'TMS for Korea'를 검색해서 설치를 누르면 플러그인이 설치되고, 카카오 지도, 네이버 지도 , VWorld 지도를 사용할 수 있게 된다.

 

웹(W) > TMS for Korea > VWorld Maps > VWorld Street 를 선택한다.

 

지도가 화면에 뜨면서 아까 내가 만들었던 레이어를 다 가린다. 레이어 창으로 가서 VWorld Street 레이어를 밑으로 끌어 당긴 다음에, 서울 행정동 레이어 우클릭 > 속성(P) 들어가서 창이 하나 뜨면 왼쪽에 심볼을 선택하고, 색상을 선택한 다음에 불투명도를 낮추면 지도랑 행정동 경계가 겹쳐서 보인다. (주의!!! 색상 바로 밑에 있는 불투명도를 낮추지 말고, 색상을 선택한 다음에 나오는 창에서 불투명도를 낮춰야 한다.)

 

색상 선택 창에서 '확인'을 눌렀으면 마지막으로, 레이어 속성 창에서 '적용'을 누르는 것도 잊지 말자.

 

 

요런 그림이 완성되었다.

 

지금은 운 좋게 좌표가 잘 맞았는데, 좌표가 안 맞으면 맞춰야 한다. 레이어마다 다른 좌표계를 쓰고 있는데, 먼저 서울 행정동 데이터는 GRS80중부(EPSG:5174), VWorld Street는 WGS84(EPSG:3857)을 사용한다. 레이어 우클릭 > 속성(P) > 원본 선택하면 '지정된 좌표계' 란에서 좌표를 수정할 수 있다. 파일을 레이어로 불러올 땐 좌표에 대한 지식이 필수이며, 보통 파일을 다운로드 하는 웹사이트에서 좌표계 정보를 알려 준다. 좌표에 해당하는 EPSG 코드를 알아야 QGIS에서 좌표를 찾을 수 있는데, 좌표 정보는 여기서 아주 정리를 잘해 놨다.

 

https://wogus789789.tistory.com/196

 

한국 주요 좌표계 EPSG코드 및 proj4 인자 정리

[전지구 좌표계] 전세계를 한번에 나타내야 할 때 많이 쓰이는 좌표계들입니다. *WGS84 경위도: GPS가 사용하는 좌표계 EPSG:4326, EPSG:4166 (Korean 1995) +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs *Bessel 1841 경

wogus789789.tistory.com

이제 우리의 구역으로 가 보자. 지도에서 충무로~종로3가까지의 구역을 확대해 놓고 다각형을 그려야 한다. 풀다운 메뉴에서 레이어(L) > 레이어 생성 > 새 Shapefile 레이어... 선택. 파일 이름은 적당히 하고 도형 유형은 '폴리곤'으로 한다.

 

'확인' 누르면 레이어 창에 내가 만든 파일 이름으로 새 레이어가 생성된 것을 볼 수 있는데, 아이콘 메뉴에서 연필 모양을 클릭해서 편집 모드를 켜고, 오른쪽에 '폴리곤 객체 추가' 아이콘을 클릭한 뒤 지도에서 원하는 사각형을 그리면 된다.

 

pvwyd

폴리곤 객체 추가 아이콘 왼쪽에 디지타이저 옵션 버튼이 있는데 '점으로 디지타이즈'를 선택하고 화면에서 원하는 사각형을 그리면 된다. (각 꼭지점에 왼클릭하고 마지막으로 우클릭하면 id 창 하나 뜨는데 그냥 엔터 누르면 끝)

 

자 이제 처음 R에서 저장했던 사업체조사 .csv 파일을 불러올 차례다. 이 파일은 x_code, y_code란 이름의 변수로 좌표 값을 가지고 있는데 QGIS에서 부르면 이 변수를 자동으로 좌표로 인식한다.

 

풀다운 메뉴에서 '레이어(L) > 레이어 추가 > 구분자로 분리된 텍스트 레이어 추가'를 선택하면 창이 뜰 것이다.

 

CSV 파일은 찾아서 넣고 도형 좌표계는 5179로 선택한다. X필드와 Y 필드는 자동으로 변수를 읽어들였다. '추가(A)'를 누르면 지도에 120만개의 점이 찍히는 시간이 걸려서 점들이 우글우글 늘어나는 모습이 좀 징그럽다.

 

 

세 개의 레이어가 좌표가 정확히 맞아서 아름다운 그림을 보여준다. (맞는 좌표를 찾느라 삽질을 참 많이 했다.) 이제 다시 우리 구역을 확대해 보자. 내가 그린 사각형 안에 있는 포인트를 따로 선택해야 한다.

 

풀다운 메뉴에서 '벡터(O) > 조사 도구(R) > 위치로 선택'을 선택하면 창이 하나 뜬다.

 

 

그림에서 보는 것과 같이 포인트 레이어와 폴리곤 레이어를 각각 선택하고 실행 누르면 폴리곤 안에 있는 포인트만 선택이 된다. 폴리곤 안에 있는 포인트가 다른 색깔로 바뀐 것을 볼 수 있다.

 

이제 선택된 포인트들만으로 이루어진 별도의 레이어를 만들어 저장한다. 레이어 창에서 포인트 레이어(sbs20co2)를 우클릭 하고 'Export > 선택한 객체를 다른 이름으로 저장'을 클릭한다. 포맷은 일단 GeoPackage로 하고, 파일 이름을 정해 주면 그 이름으로 된 새로운 레이어가 만들어진다. 새로 만들어진 레이어를 다시 우클릭하고 'Export > 객체를 다른 이름으로 저장'을 클릭한다. 이번엔 포맷을 바꿔서 엑셀(XLSX) 파일로 저장한다. 구역 내에 위치한 사업체만으로 이루어진 테이블이 생성된 것이다. 이 엑셀 파일은 R에서 불러다가 추가적인 분석을 할 수 있다.

 

 

Posted by jujaeuk
,

1. 오차행렬(confusion matrix)

실제 값이 행이 되고 예측값이 열이 되며, 해당 좌표에 실제 값과 예측값의 조합의 수를 넣어서 만든 행렬을 오차행렬이라 한다. 예를 들어 다음과 같은 3 x 3 오차행렬을 보자.

  predict
1 2 3
actual 1 5 3 4
2 21 10 9
3 16 17 15


좌표 (1, 1)에 해당하는 값 5는 정답이 1인 것을 1로 정확히 맞춘 것이 5개 있다는 뜻이다. 좌표 (1, 2)에 해당하는 값 3은 정답이 1인 것을 2라고 잘못 맞춘 것이 3개 있다는 뜻이다. 즉 이 테이블에서 정답은 (1, 1), (2, 2), (3, 3) 좌표에 해당하는 값이 된다. 전체가 100개이고 이 중 대각선 요소를 합하면 5 + 10 + 15 = 30이니 정답률은 30%가 될 것이다.

 

2. 이진 분류(binary classification)

위에 있는 분류 문제가 데이터셋을 세 가지 클래스로 분류하는 다중 분류(multiple classification)였다면 데이터셋을 두 가지 클래스로 분류하는 문제를 이진 분류(binary classification)이라 한다. 위에 있는 문제로 이진 분류로 변환할 수 있다. 예를 들어, 데이터셋을 1, 2, 3 세 가지로 분류할 게 아니라, 1인 것과 그 나머지(2, 3)로 분류한다고 해보자. 그럼 오차행렬을 다음과 같이 2 x 2 행렬로 변환할 수 있다.

 

  1 Not 1
1 5 7
Not 1 37 51

 

머신러닝에서는 이진 분류에서 '1'과 'Not 1'을 'Positive', 'Negative'로 부른다. 내 생각에는 어차피 이진 분류니 P와 N을 뒤바꿔 불러도 상관없을 것 같은데 통상 'Not'에 해당하는 것을 'Negative'라 한다. 이진 분류의 오차행렬은 다음과 같이 쓸 수 있다.

 

  (predict) Positive (predict) Negative
(actual) Positive TP (True Positive) 5 FN (False Negative) 7
(actual) Negative FP( False Positive) 37 TN (True Negative) 51

 

3. 정확도 측정 지표

정확도 측정 지표는 네 종류가 있다.

3-1. 정확도(accuracy)

3-2. 정밀도(precision)

3-3. 재현율(recall) 또는 민감도(sensitivity) = 진짜양성비율(True Positive Rate, TPR)

3-4. 특이도(specificity) = 진짜음성비율(True Negative Rate, TNR)

3-5. 거짓양성비율(False Positive Rate, FPR)

 

3-6. F1 스코어

 

정확도 하나만 있으면 될 거 같은데 왜 이렇게 많은 지표가 있는 걸까. 그 이유는 데이터의 실제 값이 어느 한 값이 압도적으로 많은 불균형 데이터인 경우에 성능이 나빠도 정확도는 높게 나올 수가 있기 때문이다. 위의 예를 보면 실제 값이 Positive인 비율은 12%이다. 즉, 무조건 Negative라고 예측해도 정확도가 88%는 나오는데 이걸 성능이 좋다고 말할 수는 없다. 일반적으로 정밀도와 재현율을 같이 보며, 둘은 트레이드오프 관계가 있다. 어느 한쪽이 너무 낮으면 좋은 분류기라고 볼 수 없다.

 

4. 분류기 성능을 볼 수 있는 두 개의 곡선

4-1. PR 곡선(정밀도/재현율 곡선)

그림에서 보는 것처럼 재현율을 높이다 보면 어느 지점에서 정밀도가 급격하게 떨어지기 시작한다. 아마도 정밀도가 급격하게 떨어지기 직전의 임계값을 갖도록 하는 것이 가장 이상적일 것이다.

 

4-2. ROC 곡선

FPR에 대한 TPR의 곡선을 ROC 곡선이라고 한다. 아래와 같이 생겼다.

곡선 아래의 면적(Area Under Curve, AUC)이 분류기의 성능이 된다. 즉, AUC=1이면 완전한 분류기이고, 완전히 랜덤한 분류기인 경우 45도 대각선(그림의 점선)이 되기 때문에 AUC=0.5가 된다.

 

1) 양성이 적거나, 거짓 음성보다 거짓 양성이 더 중요하면 → PR 곡선을 사용한다.

2) 그렇지 않으면 → ROC 곡선을 사용한다.

 

참고) 핸즈온 머신러닝 2판(오렐리앙 제롱 저, 박해선 역), 한빛미디어 pp.133-143

Posted by jujaeuk
,

Jupyter 설치하기

Jupyter 2021. 11. 27. 22:15

설치하려면 Windows+R 누르고 'cmd' 한 다음에 커맨드 창이 뜨면

pip install jupyterlab

 

실행하려면

jupyter-lab

 

괜히 conda나 다른 걸로 설치하려고 하지 말자. 운이 좋아 될 수도 있는데 그냥 성공 확률이 100%인 것 하나만 기억하면 된다.

Posted by jujaeuk
,

LaTeX, 수식 괄호 정리

LaTeX 2021. 9. 23. 23:27

소괄호, 중괄호, 대괄호가 있고, 각각 짧은 괄호, 긴 괄호가 있다. 주요 괄호 코드와 더불어, 자주 실수해서 에러가 잘 나는 부분을 함께 표로 정리했다. 쓸때마다 구글 검색해서 썼는데, 괄호는 하도 헷갈려서 쓸 때마다 계속 같은 걸 구글링하게 되더라. 확실히 정리해서 외워 놓으려고 포스팅을 올리기로 했다. 아래 표를 보면 된다.

  소괄호 중괄호 대괄호
짧은 괄호 ( ) \{ \} [ ]
긴 괄호 \left( \right) \left\{ \right\} \left[ \right]
에러가 나는 코드 \( \) \left{ \right} \[ \]

짧은 괄호라 함은 분수같이 키가 큰 수식을 둘러싸더라도 여전히 작은 괄호로 둘러싸는 걸 말하는 거고, 긴 괄호는 수식이 크면 거기에 따라서 높이가 자동으로 길어지는 괄호를 말한다. 소괄호와 대괄호는 방식이 같은데 중괄호가 조금 달라서 항상 헷갈린다.

 

자동으로 길어지는 괄호 말고 사용자가 임의로 길이를 조정할 수도 있는데 다음과 같다. 아래로 내려갈수록 커진다.

  소괄호 중괄호 대괄호
size 1 \big( \big) \big\{ \big\} \big[ \big]
size 2 \Big( \Big) \Big\{ \Big\} \Big[ \Big]
size 3 \bigg( \bigg) \bigg\{ \bigg\} \bigg[ \bigg]
size 4 \Bigg( \Bigg) \Bigg\{ \Bigg\} \Bigg[ \Bigg]

다른 옵션이 더 있지만 일단 이것만 외우자.

Posted by jujaeuk
,

우리나라의 산업통계는 2017년부터 KSIC 10차 개정을 쓰고 있다. 그런데 2017년 이전 통계도 쓸 일이 있다 보니 9차 개정을 쓸 일이 있는데 R에서 쓸 수 있는 분류표가 없어서 따로 만들기로 했다.

 

통계분류포털(kssc.kostat.go.kr) 들어가서 한국표준산업분류>자료실>최신개정 들어가면 제9차 한국표준산업분류 분류항목표라는게 있고 KSIC2007.xls라는 파일을 받을 수 있다. 엑셀로 열어보면 대략 이렇다.

KSIC 9차 분류 항목표

대-중-소-세-세세분류가 하나로 합쳐져 있다. tidy한 데이터가 없는 것이 좀 아쉽긴 한데, 그래도 이 엑셀 파일은 인쇄해서 윗사람에게 바로 보여줄 수도 있고, 약간의 가공을 거치면 R에서도 쓸 수 있으니 통계청은 상당히 효율적으로 일을 한 것이다. 이제부터 R에서 쓸 수 있는 파일로 가공을 하려고 한다.

 

먼저, 엑셀 파일을 부른다. 여러 방법이 있는데 readxl 패키지의 read_excel() 함수를 썼다. 이 함수는 header=F 같은 옵션을 쓸 수 없는 대신 col_names= 옵션을 써서 변수 이름을 정해 주면 첫 줄이 변수 이름이 되는 걸 막을 수 있다.

install.packages("readxl")
library(readxl)
ksic9<-read_excel("KSIC2007.xls",col_names=c("code","name_ko","name_en"))

결과를 보면,

> head(ksic9)
# A tibble: 6 x 3
  code                                                                name_ko     name_en         
  <chr>                                                               <chr>       <chr>           
1 "A 농업, 임업 및 어업 (01 ~ 03)\nAgriculture, forestry and fishing" NA          NA              
2  NA                                                                 NA          NA              
3 "01"                                                                농업        Agriculture     
4  NA                                                                 NA          NA              
5 "011"                                                               작물 재배업 Growing of Crops
6  NA

엑셀 파일도 그랬지만 불러진 데이터는 한줄씩 띄우기가 되어 있으니 빈 줄을 삭제한다. code 변수 값이 NA인 행을 삭제하는 명령을 준다.

ksic91<-ksic9[which(!is.na(ksic9$code)),]

첫 줄에 A로 시작하는 이름이 대분류명이다. 엑셀 파일을 스크롤해서 내려보면 알겠지만 나중에 B로 시작하는 두번째 대분류가 나오고 그럴 것이다. code 변수의 첫글자를 일단 딴다. 만약 대분류라면 알파벳 대문자일 것이고 중-소-세-세세분류라면 숫자일 것이다. 옳게 된 대분류 코드가 나오면 그대로 유지하고 숫자가 나오면 위에 옳게 된 대분류 코드로 대체하면 된다.

ksic91$code_a<-substr(ksic91$code,1,1)
for(i in 1:nrow(ksic91)){
  if(grepl("[A-Z]",ksic91[i,]$code_a)) temp<-ksic91[i,]$code_a
  else ksic91[i,]$code_a<-temp
}

substr(string, a, b) 함수는 string 문자열 변수의 a번째부터 b번째까지의 글자를 반환한다. 위의 코드는 code 변수의 첫번째 한글자만 뽑아서 code_a 변수에 넣은 것이다. grepl() 함수는 문자열 안에 특정 문자열이 포함되어 있으면 TRUE를 반환하는 함수인데, 여기서 "[A-Z]"는 정규표현식으로, "대문자 A부터 대문자 Z까지 사이의 글자 중 아무 글자"라는 뜻이다. 즉 code_a 변수에 알파벳 대문자가 있으면 temp 변수에 그 대문자를 저장하고 만약 없으면 temp가 저장하고 있는 알파벳 대문자로 code_a 변수의 값을 대체하는 것으로, 모든 산업명에 대분류 코드를 집어 넣는다.

> ksic91<-ksic91[,c("code","code_a","name_ko","name_en")]
> head(ksic91)
# A tibble: 6 x 4
  code                                      code_a name_ko              name_en                                   
  <chr>                                     <chr>  <chr>                <chr>                                     
1 "A 농업, 임업 및 어업 (01 ~ 03)\nAgricul~ A      NA                   NA                                        
2 "01"                                      A      농업                 Agriculture                               
3 "011"                                     A      작물 재배업          Growing of Crops                          
4 "0111"                                    A      곡물 및 기타 식량작~ Growing of Cereal Crops and Other Crops f~
5 "01110"                                   A      곡물 및 기타 식량작~ Growing of Cereal Crops and Other Crops f~
6 "0112"                                    A      채소, 화훼작물 및 ~  Growing of Vegetables, Horticultural Spec~
> tail(ksic91)
# A tibble: 6 x 4
  code                                                 code_a name_ko         name_en                             
  <chr>                                                <chr>  <chr>           <chr>                               
1 "U 국제 및 외국기관(99)\nActivities of extraterrito~ U      NA              NA                                  
2 "99"                                                 U      국제 및 외국기~ Extra-Territorial Organizations and~
3 "990"                                                U      국제 및 외국기~ Extra-Territorial Organizations and~
4 "9900"                                               U      국제 및 외국기~ Extra-Territorial Organizations and~
5 "99001"                                              U      주한 외국공관   Foreign Embassies                   
6 "99009"                                              U      기타 국제 및 ~  Other Extra-Territorial Organizatio~

code_a라는 변수에 A부터 U까지의 대분류 코드가 잘 들어갔다. 보시다시피 대분류는 전부 21개이다. 대분류만 따로 모아서 별도의 표를 만든다.

ksic92<-data.frame(ksic91[grep("^[A-Z]",ksic91$code),1])

grep() 함수는 문자열 변수에 특정 문자열이 포함된 행의 번호를 반환한다. 정규표현식에서 앞에 ^ 표시는 "뒤에 나오는 글자로 시작하는"이란 뜻이다. 즉 "^[A-Z]"는 "알파벳 대문자로 시작하는"이란 뜻이다. 일단 첫줄만 볼 거 같으면,

> ksic92[1,]
[1] "A 농업, 임업 및 어업 (01 ~ 03)\nAgriculture, forestry and fishing"

대분류 코드로 시작하고 한글명, (중분류 코드 범위), 영문명 이렇게 들어가 있다. 잘보면 영문명 앞에 줄바꿈표 '\n'가 들어가 있는데 이걸 이용해서 문자열을 한국어와 영어로 분리한다. strsplit() 함수를 쓰면 사이에 낀 문자열을 기준으로 문자열을 둘로 분리할 수 있는데, 리스트로 만들어준다. 두 개의 변수로 된 데이터프레임으로 만들려면 다음과 같은 짓을 해야 한다.

 

1) unlist() : 리스트를 뽀개서 문자열로 만든다. 한국어명과 영어명이 하나씩 번갈아가며 나오는 문자열 배열이 된다.

2) matrix() : 한줄로 된 배열을 2 by 21 행렬로 변환한다.

3) t() : transpose 명령으로 행과 열을 맞바꿔 21 by 2 행렬이 된다.

4) data.frame() : matrix를 data.frame으로 변환한다.

ksic921<-strsplit(ksic92[,1],"\n")
ksic922<-unlist(ksic921)
ksic923<-t(matrix(ksic922,2,21))
ksic924<-data.frame(ksic923)

ksic924 데이터의 결과를 보면 이렇다.

KSIC 9차 대분류 작업중인 데이터

이제 원하는 글자들을 뜯어서 각각 코드명, 한국어명, 영어명으로 된 대분류표를 만들 수 있다. 다 만들었으니 csv 파일로 저장까지 하자.

code_a<-substr(ksic924$X1,1,1)
name_a_ko<-substr(ksic924$X1,3,100)
name_a_en<-ksic924$X2
ksic9a<-data.frame(code_a,name_a_ko,name_a_en)
write.csv(ksic9a,"ksic9a.csv")

완성된 대분류표이다. 변수 이름에 a를 집어 넣은 건 이게 대분류이기 때문이고 앞으로 중분류, 소분류를 계속 만들것이기 때문이다.

KSIC 9차 대분류표 완성

이제 중분류를 만들어 보자. 아까 만들었던 데이터로 돌아가야 한다. ksic91에서 code가 알파벳 대문자로 시작하는 것만 따로 모아서 대분류를 만들었는데, 이제는 code가 알파벳 대문자로 시작하지 않는 것만 따로 모은다. 여기서 또 헷갈리는 정규표현식 문법이 하나 등장한다. "^[A-Z]"라고 쓰면 "A-Z 사이의 문자 중 하나로 시작하는"인데, "^[^A-Z]"라고 쓰면 "A-Z 사이의 문자가 아닌 문자 중 하나로 시작하는"이란 뜻이 된다.(^ 표시는 [] 밖에 있을 때랑 안에 있을 때 다른 뜻으로 쓰인다.)

ksic93<-data.frame(ksic91[grep("^[^A-Z]",ksic91$code),])

결과를 열어 보면,

ksic 9차 중분류 만들기

code를 보면 두자리부터 다섯자리까지 다양한데, 이제부터 편하다. 앞에 두 자리는 무조건 중분류, 한자리 더 붙여서 세자리면 소분류, ... 이렇게 세세분류까지 가니까 하나씩 뜯어서 중-소-세-세세분류를 만들면 된다.

ksic93$code_b<-substr(ksic93$code,1,2)
ksic93$code_c<-substr(ksic93$code,1,3)
ksic93$code_d<-substr(ksic93$code,1,4)
ksic93$code_e<-substr(ksic93$code,1,5)
ksic93<-ksic93[,c("code_a","code_b","code_c","code_d","code_e","code","name_ko","name_en")]

요렇게 나온다.

대중소세세세 분류코드가 다 있는 분류표

이제 code 변수를 기준으로 데이터를 추출해 중분류표, 소분류표, 세분류표, 세세분류표를 만들 수 있게 되었다. 모든 분류표는 상위분류도 갖고 있으니 얼마나 좋은가. code 변수의 글자수(자릿수)를 기준으로 두자리면 중분류표로 보내고 세자리면 소분류표로 보내고... 하면 된다. 표 분리를 하고 나면 이젠 code 변수는 필요 없으니 처치한다.

ksic9b<-ksic93[which(nchar(ksic93$code)==2),c("code_a","code_b","name_ko","name_en")]
names(ksic9b)<-c("code_a","code_b","name_b_ko","name_b_en")
ksic9c<-ksic93[which(nchar(ksic93$code)==3),c("code_a","code_b","code_c","name_ko","name_en")]
names(ksic9c)<-c("code_a","code_b","code_c","name_c_ko","name_c_en")
ksic9d<-subset(ksic93[which(nchar(ksic93$code)==4),],select=-c(code_e,code))
names(ksic9d)<-c("code_a","code_b","code_c","code_d","name_d_ko","name_d_en")
ksic9e<-subset(ksic93[which(nchar(ksic93$code)==5),],select=-code)
names(ksic9e)<-c("code_a","code_b","code_c","code_d","code_e","name_e_ko","name_e_en")

중분류표인 ksic9b만 일단 살펴보자.

완성된 ksic 9차 중분류표

상위 분류인 대분류 코드를 갖고 있는데, 만일 대분류 산업명을 붙이고 싶으면 이미 만들어놓은 대분류표가 있으니 merge() 함수를 이용해 붙이면 된다.

ksic9ba<-merge(x=ksic9a,y=ksic9b,key=code_a,all.y=T)[,c("code_a","name_a_ko","code_b","name_b_ko")]

결과를 보자.

ksic 9차 대분류, 중분류 머지

소스의 풀버전은 여기(https://github.com/jujaeuk/KSIC-9-)에서 받을 수 있다.

Posted by jujaeuk
,

파이썬에서 파일을 읽거나, 쓰거나, 고칠 땐 먼저 open() 함수로 파일을 열고, 작업이 끝났으면 close() 함수로 파일을 닫아 주어야 한다.

 

1. 파일 읽기

기본적인 코드는 다음과 같다.

f=open('mydata.txt','r')
lines=f.readlines()
for line in lines:
	print(line)
f.close()

open() 함수의 첫번째 인자는 파일 이름이고, 두번째 인자는 파일 모드이다. 'r'은 읽기, 'w'는 쓰기, 'a'는 추가하기이다.

메모장을 열어 여러 줄로 된 텍스트 파일을 하나 만들어서 mydata.txt라는 이름으로 저장하고 위의 코드를 실행해 보면 다음과 같이 나타난다.

 

안녕하세요.

주재욱입니다.

그럼 안녕히 계세요.

저게 보기 싫지 않으면 상관없는데, 한줄씩 띄어서 출력되는 게 싫은 사람도 있을 것이다. 여러 줄로 된 텍스트 파일을 읽으면 각 줄 끝에 줄바꿈 문자('\n')가 같이 변수에 저장되는데 파이썬의 print() 함수는 줄바꿈 문자가 있건 없건 알아서 줄을 바꿔주기 때문에 줄바꿈이 두 번 일어나서 그렇다. print() 함수에게는 줄바꿈을 못하게 하는 옵션이 없기 때문에 각 문자열 끝에 있는 줄바꿈 문자를 없애 줘야 하는데 strip() 함수를 쓰면 된다. strip() 함수는 문자열 양 끝에 줄바꿈 문자나 공백을 없애 주는 함수이다.

f=open('mydata.txt','r')
lines=f.readlines()
for line in lines:
	print(line.strip())
f.close()

실행시키면 한줄씩 띄어서 출력되던 것이 줄이 붙어서 출력되는 걸 알 수 있다.

안녕하세요.
주재욱입니다.
그럼 안녕히 계세요.

2. 파일 쓰기

오픈 함수의 두번째 인자인 모드를 'w'로 바꾸면 파일 쓰기가 가능해진다. 파일에 텍스트를 쓸 때는 write() 함수를 쓴다.

f=open('mydata.txt','w')
f.write('안녕하세요.\n')
f.write('homy입니다.\n')
f.write('히히, 재밌다.\n')
f.close()

3. with-as 문

with-as문을 쓰면 파일을 열어서 작업을 하고 파일을 닫을 때까지의 과정을 블럭화한다. 블럭을 빠져 나오면 파일은 자동으로 닫히기 때문에 close()문을 따로 쓸 필요가 없다.

with open('mydata.txt','w') as f:
	f.write("안녕하세요. homy입니다.\n");
	f.write("with-as 문을 테스트하고 있습니다.\n");

4. 파일의 특정 열을 삭제하기

파일 전체를 읽어들인 뒤, 열을 뺀 나머지를 새 파일에 다시 쓰는 방식으로 삭제한다. 이를테면 10번째 행을 삭제하고 싶다면 다음과 같이 하면 된다.

with open('mytext.txt','r') as f:
	text=f.readlines()
i=0
with open('mytext.txt','w') as f:
	i=i+1
	for line in text:
		if i!=10: f.write(line)

5. csv 파일 읽고 쓰기

통계 데이터로 많이 쓰이는 csv 파일은 컬럼이 콤마(,)로 구분되어 있다. 문자열을 처리하는 함수를 이용하면 직접 만들 수도 있겠지만 csv 모듈을 쓰면 편하게 불러서 쓸 수 있다.

import csv
with open('mydata.csv','r') as f:
	rdr=csv.reader(f)
	for line in rdr:
		print(line)

csv 모듈의 reader() 함수를 써서 csv 파일을 불렀고 데이터는 rdr이란 변수에 저장되었다. rdr은 _csv.reader라는 타입의 변수인데 리스트 변수는 아니지만 for 문을 사용해 각 행을 읽어 들이는 것이 가능하다. 여기서 line이란 변수는 데이터의 각 행에 해당하는데 리스트 변수이고, 각 열이 요소로 저장되어 있다. print 함수를 써서 보면 리스트 형태로 출력을 한다.

파일을 쓸 때는 writerow() 함수를 쓴다. 열 단위로 쓰게 되어 있다.

import csv
with open('mydata.csv','w') as f:
	wr=csv.writer(f)
	wr.writerow([1,2,3,4])
	wr.writeriw([5,6,7,8])

6. csv 파일 sort 하기

csv 파일은 변수 하나를 기준으로 정렬이 가능하다. operator 모듈의 itemgetter() 함수를 써서 변수를 불러들인다. 이를테면 세번째 변수를 기준으로 정렬을 하고 싶으면 다음과 같이 하면 된다.

import csv
import operator
with open('andy.csv','r') as f:
	rdr=csv.reader(f)
	sort=sorted(rdr,key=operator.itemgetter(2))
	for line in sort:
		print(line)

itemgetter(2)가 세번째 변수를 정렬 기준인 key로 사용하겠다는 뜻이다. 파이썬은 0부터 세니까 첫번째 변수는 itemgetter(0)이고 세번째 변수는 itemgetter(2)이다. lambda를 쓰면 좀 더 세련되게 쓸 수 있다.

import csv
import operator
with open('andy.csv','r') as f:
	rdr=csv.reader(f)
	sort=sorted(rdr,key=lambda x:x[2])
	for line in sort:
		print(line)

위에 것과 완전히 같은 결과를 얻을 수 있다.

Posted by jujaeuk
,

Steam에서 설치한 게임은 Steam을 먼저 실행해서 내 라이브러리에 들어가면 실행할 수 있지만, 게임을 단축아이콘으로 만들어 바탕화면이나 시작메뉴에 갖다 놓으면 Steam을 실행하는 과정을 생략하고 바로 게임을 실행할 수 있다. (근데 사실은 보이지만 않을 뿐이지 Steam은 실행된다. 대신 사용자는 한번의 클릭만으로 바로 게임을 실행할 수 있으니 편리하다.)

 

바탕 화면에 갖다 놓은 Steam 단축 아이콘을 우클릭하면 "시작화면에 고정(P)"이란 메뉴가 있고 이걸 선택하면 윈도우10 시작화면에 보낼 수 있다.

Steam 아이콘을 우클릭했다. 이렇게 우클릭하면 이 메뉴가 뜨는 것이 정상이다.

그런데, Steam 라이브러리에서 바탕화면으로 가져온 게임(이를테면 카탄 유니버스)의 단축 아이콘은 우클릭을 해도 아까 보였던 "시작 화면에 고정(P)" 메뉴가 보이지 않는다. 왜? 이유는 나도 모른다.

카탄 단축아이콘을 우클릭하면 "시작 화면에 고정(P)"이 없다.

그래서 방법을 궁리해 봤는데 다음과 같은 방법을 쓰면 해결된다. 먼저, 시작 메뉴 왼쪽에 Steam 폴더를 열고 Steam 아이콘을 우클릭해 "자세히" > "파일 위치 열기" 순서로 클릭하고 들어가면 Steam 단축 아이콘이 있는 폴더를 열 수 있다.

파일 위치 열기를 클릭하고 들어간다

그 다음에, 바탕화면에 있는 게임 아이콘을 Steam 단축 아이콘이 있는 시작메뉴 폴더로 옮긴다.

그냥 드래그 앤 드롭을 하면 된다.

 

시작메뉴 폴더에 옮겨진 단축 아이콘을 우클릭해 봐도 여전히 "시작 화면에 고정(P)"은 없다. 아직 할 일이 남았다.

여전히 이유는 알 수 없다.

 

다시 화면 하단 좌측 위도우 버튼을 누르고 시작메뉴를 띄워 왼쪽 Steam 폴더를 찾아가 보자. 시작메뉴의 Steam 폴더 안에 게임 아이콘이 보일 것이다. 이걸 우클릭하면 이젠 드디어 "시작 화면에 고정" 메뉴가 보인다!

왜 이렇게 해야만 이게 뜨는지는 여전히 알 수 없다.

시작 화면에 고정을 클릭하고 나면,

이젠 뜬다!

이제 적당한 위치에 갖다 놓고 쓰면 된다.

 

P.S. 이 포스팅이 왜 보드게임 카테고리냐고? 카탄은 원래 보드게임이니까.

Posted by jujaeuk
,

스마트시티?

시사 2020. 8. 17. 16:52

정책연구자에게 ‘스마트(smart)’라는 말은 매우 곤혹스러운 용어이다. 도무지 뜻을 종잡을 수 없기 때문이다. 컴퓨터 기술이 발달하고 많은 분야에 활용되면서 사람들은 다양한 기기 또는 서비스들에 ‘e(electronic)’, ‘i(intelligent)’, u(ubiquitous)’ 또는 ‘스마트’라는 수식어를 붙이기 시작했다. ‘스마트’는 왜 이전의 다른 인기어들처럼 s라고 축약해서 쓰지 않았을까? 대략 세 가지 이유가 추측된다. 첫째, ‘스마트’는 유일하게 한 음절로 이루어진 단어이기 때문에 짧고 부르기 편해서 그랬을 것이다. 둘째, 기술적 냄새가 물씬 풍기는 이전 단어들과 달리 일상에서 쓰는 단어였기 때문에 감성을 자극했을 수도 있다. 셋째, e나 i는 전자 또는 IT를 상징하는 알파벳으로 비교적 역사가 깊다. (u는 좀 뜬금 없지만 어차피 풀어 쓴 단어도 매우 낯설기 때문에 아마도 신비감을 어필하느라 그랬을 것이다.) 반면 s는 IT랑은 상관없었고 스마트 외에 연상되는 다른 단어가 너무 많다. (이를테면 special이라던가) 어쨌든 e, i, u, 스마트 모두 컴퓨터나 인터넷을 활용한다는 뜻을 갖고 있고, 이름에 저 말이 붙으면 언제나 환영을 받았다.


‘스마트’의 인기에 힘입어 언제부턴가 ‘스마트시티’란 말이 등장했다. 이 단어가 얼마나 맘에 들었는지 사람들은 기존의 U-시티 법을 아예 고쳐서 유비쿼터스시티를 죄다 스마트시티로 바꿔 버렸다. 여러 가지 신기술을 통해 도시 문제가 해결되는 도시라니! 생각만 해도 가슴 설렌다. 그런데 조금만 생각해 보면 뭔가 이상하다는 것을 느낄 수 있다. 다음과 같은 이유로 스마트시티는 기존의 도시와 차별화될 수 없다.


첫째, 신기술을 적용해 만드는 도시라는 건 새로운 개념이 아니다. 도시를 지탱하는 기술은 언제나 꾸준히 발전해 왔고 그에 따라 도시도 발전해 왔다. 스마트시티법에 따르면 ‘건설기술과 정보통신기술을 융복합하여 다양한 서비스를 제공하는 도시’를 스마트시티라 하는데, 그렇다면 지금 우리나라의 도시는 이미 스마트시티이다. 많은 사람이 의식하지 않고 그냥 지나치지만, 우리가 일상생활에서 접하는 도시서비스들은 알고 보면 엄청난 신기술의 집합체들이다. 대중교통 환승 시 이동구간에 따라 요금 할인 폭이 달라지고, 정류장에 서 있으면 내가 기다리는 버스가 도착할 때까지 예상 대기시간을 알 수 있으며, 광역버스는 버스 외벽에 잔여 좌석 수가 표시된다. 모든 택시는 GPS 내비게이션을 사용해 목적지까지 최단 경로에 대한 안내를 받는다. 서울에 있는 대부분의 주차시설은 이제 주차 카드를 뽑지 않고 번호판 인식으로 입장하며, 건물 내 상점에서 주차 할인을 받을 경우, 내차 번호 네 자리를 점원에게 알려주기만 하면 된다. 그 밖에도 헤아릴 수 없는 많은 도시서비스가 첨단기술을 활용해 제공되고 있다.


이런 서비스는 도시 전역에 걸쳐 매우 안정적으로 제공되는 통신망과 전력망 외에도 방대한 양의 데이터를 짧은 시간 내에 처리할 수 있는 정교한 소프트웨어와 오차 없는 센서 기술을 필요로 한다. 그리고 앞서 언급한 서비스들은 대부분 첫 번째 아이폰이 나온 시기보다 늦게 대중교통 서비스에 도입됐을 만큼 새로운 기술들이다. 유비쿼터스시티나 스마트시티에 대한 논의를 시작한 사람들은 막연히 ‘첨단 기술로 만드는, 지금보다 훨씬 나은 도시’를 염두에 두고 법을 만들고 청사진을 만들었겠지만 도시는 언제나 쉼 없이 꾸준히 기술적으로 발전해 왔다. 기술은 많은 도시의 과제를 해결해 왔으며, 지금까지도 해결하지 못한 도시 문제들은 개선되고는 있지만 언제 어느 수준으로 개선될지는 아무도 모른다. 도시를 구성하는 기술의 이러한 속성을 이해하고 나면 ‘스마트시티란 무엇인가’라는 질문에 대답하기가 더욱 어려워진다.


둘째, 그렇다면 매우 획기적인 기술로 인해 지금과는 크게 다른 도시를 만들 수 있다면 스마트시티라 불러도 되는 것일까? 기술의 획기적인 발전이 있다 하더라도 도시는 획기적으로 변할 순 없다. 도시가 가진 경로의존성 때문이다. 생물 종들은 진화할 때 시작과 끝을 연결하는 가장 효율적인 경로를 선택해 진화하는 것이 아니라 각 단계에서의 생존을 최우선 과제로 설정해 다음 단계로 진화하기 때문에 현재의 상태와 지금까지 진화해 온 경로에 크게 영향을 받는다. 도시도 이러한 경로의존성을 매우 강하게 갖고 있다. 도시 인프라는 너무나 거대하기 때문에 아무리 좋은 기술이 나와도 시스템을 근본적으로 바꿀 순 없다. 인프라 교체시 소요되는 비용을 감당할 수 없다는 이유도 있지만, 지금까지의 생활방식에 익숙해져 있는 시민들에게 하루아침에 그 방식을 바꾸라고 강요할 수 없기 때문이다.


예를 들어, 현재 활발히 연구되고 있는 자율주행 자동차의 경우, 언젠가 먼 미래에는 일반도로에 오직 자율주행차만이 달리게 될 것이라고 누구나 예상할 수 있다. 그게 사람에게 훨씬 편하기 때문이고 자율주행차의 운전능력은 언젠가 인간을 까마득히 앞지를 것이기 때문이다. 현재 자율주행차의 개발에서 가장 큰 난관을 겪고 있는 분야는 인간이 운전하는 차량과의 상호작용이다. 사람의 도로운전을 금지시키고 일반도로를 자율주행차 전용도로로 만든다면 현재 연구 개발 과정에서 발생하는 많은 어려움을 극복하고 자율주행 중심의 미래 교통시스템에 보다 빨리 다가갈 수는 있다. 하지만 그것이 불가능하기 때문에 자율주행차는 경로의존적 발전과정을 밟을 수밖에 없다. 획기적인 기술은 많지만 그것을 도시에 적용하려면 대부분 경로의존성의 문제에 직면한다. 유일한 해법은 기존의 시스템과 공존하면서 점진적으로 변화하는 것이다. 이러한 경로의존성은 스마트시티인 것과 스마트시티가 아닌 것의 구분을 더욱 어렵게 만든다.

 

셋째, 우리가 안고 있는 도시 문제는 첨단기술만으로 해결되지 않으며, 만약 기술로 해결할 수 있는 도시 문제가 있다면 스마트시티의 개념을 빌리지 않아도 해결이 가능하다. U-시티법을 스마트시티법으로 개정하는 과정에서 실패한 U-시티의 전철을 밟지 말자는 취지로 몇 가지 개념이 추가되었고 이는 개정된 법이나 시행령 또는 정부의 기본계획에도 반영되었다. 그 주요 내용은 민간의 참여 기회를 늘려 정부 주도에서 탈피하고, 교통, 방범 등 도시 인프라 중심에서 벗어나 행정, 교육, 환경 등 다양한 영역을 포함하며, 신도시 중심의 개발사업에 국한했던 것을 도시재생을 포함하여 기존 도시에까지 그 적용 대상을 확대하자는 것이다. 하지만 이러한 내용은 스마트시티와 상관없이 해결해야 하는 문제들이고, 또한 스마트시티법과 상관없이 해결할 수 있는 문제들이다. 행정의 스마트화는 정부가 ‘국가정보화기본계획’을 중심으로 1990년대 초부터 꾸준히 추진해 왔으며 한때 UN 전자정부 평가에서 3년 연속 세계 1위를 차지하는 등 소기의 성과도 있었는데 U-시티 또는 스마트시티와는 관계가 없다. 교육 분야에서는 교육행정정보시스템(NEIS)이 2002년부터 구축되어 사용되고 있으며 이 또한 스마트시티와 관계없다. 다시 말하면 스마트시티가 말하는 차별점이라는 것은 전통적인 도시 기능 즉 인프라 영역을 제외하면 차별화할 내용이 필요하기 때문에 적은 것일 뿐 그것이 도시 기능과 결합하면 무엇이 어떻게 더 좋아지길래 그래야만 하는지에 대한 당위성이 부족하다.


현재 서울을 비롯한 도시가 직면하고 있는 문제는 매우 다양하다. 최근 미국과 유럽 등 주요 선진국들의 흐름을 보면 지역의 경제적 발전을 위해서는 불평등, 고령화, 사회갈등, 실업과 같은 고질적인 사회문제를 함께 해결하는 것이 중요하다는 것에 인식을 같이하고 있다. 도시가 안고 있는 사회문제를 해결하는데 첨단기술을 활용할 수 있다면 분명 의미 있는 일이 될 것이다. 하지만 기술을 어떻게 활용해 사회문제를 해결할 것인가에 대해서는 아직 누구도 명확한 비전을 제시하지 못하고 있다.


앞으로 신기술을 활용한 새로운 도시서비스는 계속 등장할 것이다. 도시가 직면한 시대적 과제를 인식하고 시민이 바라는 도시의 미래상을 구현하기 위해 때로는 통합적인 접근이 필요할 것이다. 하지만 지금의 도시는 너무나 크고 넓으며, 여러 가지 문제들이 복잡하게 얽혀 있어서 이를 모두 아우를 큰 틀에서의 방향을 제시한다는 것은 어쩌면 의미 없을 만큼 지난한 과정이 될 것이다. 또한 스마트시티는 U-시티가 처음 등장했을 때부터 지금까지 꽤 오랜 시간이 흘렀음에도 불구하고 도시 문제 해결에 있어서 기술이 갖는 위상과 역할에 대한 논의가 진전되지 못했던 것이 아쉽다. 앞으로 더 많은 논의와 다양한 시도가 필요할 것이다. 코로나 바이러스로 미증유의 사태를 겪고 있는 지금 무엇이 진정으로 스마트한 도시인가에 대한 발상의 전환이 필요하다.

* 이 글은 서울시산학연협력포럼에서 요청받고 쓴 글임

Posted by jujaeuk
,