목차


이번 글에서는 앞에 다룬 내용을 베이스로 하는 예제 문제들을 다룰 것입니다. 글로만 이해해서는 완벽하게 익히기 어렵습니다. 정확히는 본인이 무엇을 모르는지 모르는 경우가 대부분입니다. 대부분의 공부가 그렇듯이 실제로 데이터를 다루면서 습득하는 것이 빠르고 정확한 방법이라고 생각합니다.

문제 난이도가 그렇게 쉽지는 않습니다. 왜냐하면 데이터를 전처리하는 과정은 생각보다 까다롭기 때문입니다. 적용함수를 통해서 예외가 되는 값들을 다 처리하려고 하면 이상한 데이터 몇몇 때문에 오류출력이 되는 경우가 태반입니다. 그래도 다행인것은 예제로 다루는 파일은 현장에서 구하는 데이터들보다는 깔끔하게 정리가 되어있는 편입니다. 원본 데이터(raw data)에 익숙해지면서 이런 형식의 데이터를 어떻게 해야 분석하기 편하게 바꿀 수 있는 지 고민해보는 좋은 문제가 되기를 바랍니다.


문제 1 (총 10개)

다음 내용을 담은 데이터 프레임 생성 후 아래 문제들을 순서대로 풀어보시오. 데이터 프레임의 이름은 data1로 하시오.

name = ['apple','mango','banana','cherry']

price = [2000, 1500, 500, 400]

qty = [5,4,10,NA]

데이터 프레임 생성
data1 = DataFrame({'name' : ['apple','mango','banana','cherry'],
                   'price' : [2000, 1500, 500, 400],
                   'qty' : [5,4,10,NA]})


1) mango의 price와 qty 선택

정답
# 위치 색인
df1.iloc[1,[1,2]]
df1.iloc[1:2,1:3]

# 조건 색인
data1.loc[data1.name == 'mango',:]


2) mango와 cherry 의 price 선택

정답
# 위치 색인, 차원 축소, Series 출력
data1.iloc[[1,3],1] 

# 위치 색인, 차원 축소 방지, DataFrame 출력
data1.iloc[[1,3],1:2]

# 조건 색인(모듈 numpy의 in 연산자)
data1.loc[np.in1d(np.array(data1.name),['mango','cherry']),'price']
	
# 조건 색인(모듈 pandas in 연산자)
data1.loc[data1.name.isin(['mango','cherry']),'price']


3) 전체 과일의 price만 선택 (조건 색인)

정답
data1.loc[:,'price']


4) qty의 평균 구하기

정답
# pandas 기반/ 3개 평균
data1.qty.mean()
data1.loc[:,'qty'].mean()

# numpy 기반
# 3개 평균
np.mean(data1.qty) 
# 4개 평균
np.where(data1.loc[:,'qty'].values > 0 ,data1.loc[:,'qty'].values,0).mean()
전처리 시 평균을 구할 때 NaN는 보통 무시합니다. 그래서 1명의 NaN를 포함한 4명의 평균을 구할 때 4로 나눌지 3으로 나눌지에 따라서 사용하는 코드가 달라집니다.


5) price가 1000 이상인 과일 이름 출력

정답
data1.loc[data1.price >=1000, 'name']


6) cherry, banana, mango, apple 순 출력

정답
#위치 색인을 통한 출력
data1.iloc[[3,2,1,0],:]

# 정렬을 이용한 출력
data1.sort_values('price')


7) 행 qty의 행이름을 QTY로 수정

정답
# 오류
data1.columns[2] = 'QTY'

# 권장 방식
a1 = data1.columns.values()
a1[2] = 'QTY' # 바꿀 원소만 수정
data1.columns = a1


8) name에 ‘a’를 포함하는 행 출력 (난이도 중)

정답
# lambda를 이용한 풀이
f1 = lambda x : 'a' in x
vbool = list(map(f1,data1.name))
data1.loc[vbool, :]

# 리스트 내포 표현식을 이용한 풀이
data1.iloc[[i for i in range(4) if data1.name[i].find('a') != -1],:]


9) name값을 행 이름(인덱스 이름)으로 설정 후 name 컬럼 제외

정답
# 순서대로 진행
data1.index = data1.name
data1.drop('name',axis = 1, inplace = True)

# 한번에 진행
data1.set_index('name', inplace = True)


10) apple과 cherry 행 삭제

정답
# 색인 이용
data1.loc[~data1.name.isin(['apple','cherry']),:]

# drop 메소드 이용
data1 = data1.drop(['apple','cherry'], axis = 0)


문제 2. 파일 불러온 후 전처리

아래 subway2.csv 파일을 받아서 기본 디렉토리에 넣고 python으로 불러오시오.

subway2.csv

정답
data2 = pd.read_csv('subway2.csv', engine = 'python', skiprows = 1)


그리고 나서 아래 형식으로 변경하시오.

전체    구분   5시    6시    7시 ...
서울역  승차 17465  18434  50313 ...
서울역  하차 ....
  1. 전체 컬럼의 빈 칸에 윗 칸의 역 이름 입력
  2. 컬럼명을 다음 형식으로 변경 => 01~021시로 변경
  3. 숫자에 있는 콤마(,)를 삭제 후 정수로 변환
정답
# 전체 컬럼 수정
# 1. NaN을 이전 행의 자료로 붙이기
data2.전체 = data2.전체.fillna(method = 'ffill')
# 2. 괄호 지우기
data2.전체 = data2.전체.map(lambda x : x[:x.find('(')] if x.find('(') != -1 else x)

# 컬럼의 시간대 부분 수정
# lambda를 이용
a2 = data2.columns.values
a2[2:] = list(map(lambda x : f'{int(x[:2]):#d}시', a2[2:]))
data2.columns = a2

# Value 부분 수정
# 적용함수 이용
data2.iloc[:,2:] = data2.iloc[:,2:].applymap(lambda x : int(x.replace(',','')))


2) 각 역별 하차의 총 합

정답
# 색인 이용
data2.loc[data2.iloc[:,1] == '하차'].iloc[:,2:].apply(sum, axis = 1)


오류시 해결 코드 참고


3) 승차의 시간대별 총 합

정답
data2.loc[data2.구분 == '승차',].iloc[:,2:].sum(axis = 0)


4) top(data,n=5) 함수 생성, 각 역별 승차가 많은 top 5 시간대 출력​

(난이도 상)

정답
def top(data, n=5):
    return [[data.iloc[i,0]] + list(data.iloc[i,2:].sort_values(ascending = False)[:n].index) for i in range(data.shape[0])]

top(data2.loc[data2.구분 == '승차',])


5) 하차 인원의 시간대별 각 역의 차지 비율 출력

정답
# 예전 풀이
data2.loc[data2.iloc[:,1] == '하차'].iloc[:,2:].div(data2.loc[data2.iloc[:,1] == '하차'].iloc[:,2:].apply(sum, axis = 1), axis = 0).mul(100)

# 역 이름을 인덱스로 변경
data3 = data2.loc[data2.구분 == '하차',].set_index('전체').iloc[:,1:]

data3.apply(lambda x : x/data3.sum(axis = 1) * 100)


문제 3

employment.csv 파일을 불러오고 아래 문제를 푸시오

employment.csv

1) 년도와 각 항목(총근로일수, 총근로시간…)을 멀티 컬럼(열의 멀티 인덱스)으로 설정

정답
# csv 파일 불러오기
emp = pd.read_csv('employment.csv', engine = 'python', header = None)
# 인덱스로 변경
emp.set_index(0, inplace = True)
emp.index.name = None
#전처리 후 멀티 컬럼으로 설정 후 원본 행 제거
emp.columns = [list(emp.iloc[0,:]),[x[:x.find(' ')] for x in emp.iloc[1,:]]]
emp = emp.iloc[2:,:]


2) 정규근로자의 월급여액을 년도별로 출력

정답
emp.loc[emp.index == '정규근로자'].xs('월급여액',1,1)


3) 각 년도별 근로자의 총근로일수의 평균 출력

정답
# 천 단위 구분기호 (,) 삭제, 하이픈(-)을 NaN으로 변경, float으로 변경
emp = emp.applymap(lambda x : x.replace(',','')).replace('-',NA).astype(float)

# 문제 풀이
emp.xs('총근로일수',1,1).mean(axis = 1)


4) 각 년도별 정규근로자와 비정규근로자의 월급여액의 차이 계산

정답
emp.xs('월급여액',1,1).iloc[2,:] - emp.xs('월급여액',1,1).iloc[3,:]


문제 4

1) hospital.csv 파일을 읽고 다음과 같은 데이터프레임으로 만들어라

hospital

               2013    2012
             1 2 3 4 1 2 3 4 ....
내과 강남구
      강동구
....
정답
# 멀티 인덱스
# hospital.csv 불러오기
hos = pd.read_csv('hospital.csv', engine = 'python', skiprows = 1)
# 필요없는 열 제거 후 2개 열을 인덱스로 전환
hos = hos.iloc[:,[0,1] + [i for i in range(4,hos.shape[1])]].set_index(['표시과목','시군구명칭'])
# 멀티 인덱스 이름 제거
hos.index.names = [None, None]

# 멀티 컬럼
hos.columns = [[x[:4] for x in hos.columns], [x[6] for x in hos.columns]]
# 계(total) 행 제거 hos = hos.iloc[1:,:]


2) 성형외과의 각 구별 총 합을 출력

정답
hos.xs('성형외과',0,0).sum(axis=1)


3) 강남구의 각 표시과목별 총 합 출력

정답
hos.xs('강남구',0,1).sum(axis=1)


4) 년도별 총 병원수의 합 출력

정답
hos.sum(axis = 1, level = 0).div(4)



이 부분까지 교육을 받고서 문제를 풀었던 기억이 아직도 생생합니다. 직접 엑셀 파일을 살펴보고, 문제를 어떻게 이해해야 할지, 오류가 발생하면 왜 발생하는지 찾아보고 … 수 차례 복습하면서 전처리를 하는 과정은 익숙해졌지만 새로운 데이터를 살펴볼 때마다 새로운 문제에 직면하는 것은 저만의 일이 아니라고 생각합니다. 현업에서는 각 나라별로 사용하는 날짜 형식의 차이로 인해 파일 자체가 쓰레기가 되는 경우도 있다고 합니다. 열 하나에 여러 포맷이 섞여버리면 컴퓨터에 익숙하고를 떠나서 분류 자체가 어려우니까요.

문제를 하나하나 풀면서 동시에 풀이를 옮기는 작업을 진행하였습니다. 오랜만에 문제를 풀어보면서 과거의 풀이와 비교하는 것도 재밌었습니다. 다 풀어보신 분들은 아시겠지만 데이터 전처리는 노가다의 연속이라는 것을 알 수 있습니다. 데이터 수집에 대한 메뉴얼이 없는 곳에서는 매번 코드를 만들어야 할 것 같다는 느낌이 듭니다. 필요한 변수가 없으면 외부 데이터를 가져오거나 기존 데이터를 이용해서 새로운 변수를 만들고, 배운대로 코드를 짜봤지만 오류가 발생하는 경우도 많고. 분석 모델에 넣는 과정은 아직 시작도 안했는데 험난하기만 합니다.

사실 분석 모델에 잘 정리된 데이터를 넣는 것은 굉장히 간단합니다. 구글링을 하면 분석 모델별로 어떻게 코드를 입력해야하는지 다 나와있기 때문에 분석가의 시간을 가장 많이 잡아먹는 과정은 모델 분석 이전까지의 과정입니다.

다음 글에서는 전처리에 필요한 추가적인 내용을 알아보도록 하겠습니다. R의 reshape2 패키지처럼 long data와 wide data를 다루는 함수, 그룹 함수, 구간 분할 등 … 이후에 시각화 과정, 분석 모델을 사용하는 법까지 나갈 예정입니다.


문제 2 오류시
# replace(',','')까지 했는데 컬럼들의 데이터 타입이 object 일때
data2 = pd.merge(data2.iloc[:,:2],
         data2.iloc[:,2:].astype('int'),
         left_index = True,
         right_index = True)