오늘은 공공데이터 사이트에서 가져온 '부동산 데이터'를 이용해서 전처리하는 실습을 진행하겠다.
처음에는 무조건 필요한 모듈을 import하고 dataframe을 로드하면서 시작한다.
# 데이터 프레임 로드
>>> df = pd.read_csv('https://bit.ly/ds-house-price')
>>> df
지역명 규모구분 연도 월 분양가격(㎡)
0 서울 전체 2015 10 5841
1 서울 전용면적 60㎡이하 2015 10 5652
2 서울 전용면적 60㎡초과 85㎡이하 2015 10 5882
3 서울 전용면적 85㎡초과 102㎡이하 2015 10 5721
4 서울 전용면적 102㎡초과 2015 10 5879
... .. ... ... .. ...
4500 제주 전체 2020 2 3955
4501 제주 전용면적 60㎡이하 2020 2 4039
4502 제주 전용면적 60㎡초과 85㎡이하 2020 2 3962
4503 제주 전용면적 85㎡초과 102㎡이하 2020 2 NaN
4504 제주 전용면적 102㎡초과 2020 2 3601
[4505 rows x 5 columns]
1. coumn 재 정의 (rename)
- '분양가격(㎡)'은 특수문자가 들어간 것이므로 바꿔주자.
>>> df = df.rename(columns = {'분양가격(㎡)' : '분양가격'}) # 딕셔너리 형식으로 넣어준다.
>>> df.head(2)
지역명 규모구분 연도 월 분양가격
0 서울 전체 2015 10 5841
1 서울 전용면적 60㎡이하 2015 10 5652
2-1. 빈 값과 Data Type 확인하기
- null의 개수와 데이터 타입을 확인하기 위해서는 info() 메서드를 이용하면 된다. (이전까지 자주 써온 메서드니 기억할 것이다!)
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4505 entries, 0 to 4504
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 지역명 4505 non-null object
1 규모구분 4505 non-null object
2 연도 4505 non-null int64
3 월 4505 non-null int64
4 분양가격 4210 non-null object # '분양가격' 컬럼에만 약 300개의 null값이 있다는 것을 확인.
dtypes: int64(2), object(3) # 타입은 int형 2개와 object 3개로 이루어져있음을 확인.
memory usage: 176.1+ KB
2-2. 통계값 확인하기
- 이전에 배운 describe() 메서드를 이용하여 통계 정보를 확인한다.
- describe() 메서드는 수치연산이 가능한 컬럼만 보여준다. 즉, int형만 보여줄 것이다.
- 분양가격이 object인 것도 알 수 있게 된다. (위의 코드를 통해서도 알 수 있지만)
>>> df.describe()
연도 월
count 4505.000000 4505.000000
mean 2017.452830 6.566038
std 1.311432 3.595519
min 2015.000000 1.000000
25% 2016.000000 3.000000
50% 2017.000000 7.000000
75% 2019.000000 10.000000
max 2020.000000 12.000000
3. 분양가격 column을 int 타입으로 변환
- 타입변환 메서드는 astype()을 이용한다.
# 하지만 에러가 발생한다. 왜일까?
>>> df['분양가격'].astype(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\generic.py", line 5546, in
astype
new_data = self._mgr.astype(dtype=dtype, copy=copy, errors=errors,)
File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\managers.py", lin
e 595, in astype
return self.apply("astype", dtype=dtype, copy=copy, errors=errors)
File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\managers.py", lin
e 406, in apply
applied = getattr(b, f)(**kwargs)
File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\blocks.py", line
595, in astype
values = astype_nansafe(vals1d, dtype, copy=True)
File "C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\dtypes\cast.py", line 972,
in astype_nansafe
return lib.astype_intsafe(arr.ravel(), dtype).reshape(arr.shape)
File "pandas\_libs\lib.pyx", line 614, in pandas._libs.lib.astype_intsafe
ValueError: invalid literal for int() with base 10: ' '
# 데이터에 공백이나 null값이 많기 때문이다. (위에서 null값이 많은 것은 확인했다!)
- strip() 을 활용하여 공백이 있는 데이터의 공백을 없앨 수 있다.
>>> df['분양가격'] = df['분양가격'].str.strip() # 공백을 제거하고 다시 넣는다.
>>> df.loc[df['분양가격']==' '] # 공백이 한개 있는 데이터 찾기
Empty DataFrame
Columns: [지역명, 규모구분, 연도, 월, 분양가격]
Index: []
>>> df.loc[df['분양가격']==' '] # 공백이 두개 있는 데이터 찾기
Empty DataFrame
Columns: [지역명, 규모구분, 연도, 월, 분양가격]
Index: []
# 둘다 없다고 한다.
# 하지만 다시 타입변환을 해보면 다음과 같이 에러가 뜬다.
# ValueError: invalid literal for int() with base 10: ''
# 빈값이 있는 것이다. 빈 값 데이터에 0을 넣어준다.
df.loc[df['분양가격'] == '', '분양가격'] = 0 # df['분양가격']이 ''인 행에 대해서 '분양가격' 열에만 0을 넣어라.
# loc 메서드에 대해 말하면, loc[행, 열] 순으로 작성한다.
* 이쯤에서 loc 메서드는 행과 열을 조회하는 역할을 하는 유용한 녀석이란걸 느낄 것이다.
- 그 후에 다시 타입 변환을 하니 이번엔 'ValueError: cannot convert float NaN to integer ' 에러가 뜬다.
Nan값이 존재하다는 것이다. Nan값도 0으로 채워주겠다.
>>> df['분양가격'] = df['분양가격'].fillna(0)
- 다시 타입 변환을 해보면, 'ValueError: invalid literal for int() with base 10: '6,657' ' 라고 뜬다. 콤마 데이터 때문에 int형으로 변환을 못한다는 에러이다.
- 콤마 데이터를 없애보자.
# 먼저 콤마데이터가 어디에 있는지 확인
>>> df.loc[df['분양가격']=='6,657']
지역명 규모구분 연도 월 분양가격
2125 서울 전체 2017 11 6,657 # 2125번 인덱스에 정말 있다.
# 콤마를 제거한다.
>>> df['분양가격'] = df['분양가격'].str.replace(',', '')
# 제거한 후, 해당 인덱스로 자료를 확인한다. iloc 메서드를 사용한다.
>>> df.iloc[2125]
지역명 서울
규모구분 전체
연도 2017
월 11
분양가격 6657 # 콤마가 사라진 것을 확인!
Name: 2125, dtype: object
- 다시 타입변환을 해보면? 'ValueError: cannot convert float NaN to integer ' 에러.... 콤마를 지우면서 Nan이 또 생긴 것다..
>>> df['분양가격'] = df['분양가격'].fillna(0)
- 또 타입변환 -> 'ValueError: invalid literal for int() with base 10: '-' ' 에러 발생:) 에러의 연속이구나. 이번엔 하이픈('-')이 있다고 한다. 하이픈을 제거해보자.
>>> df['분양가격'] = df['분양가격'].str.replace('-', '')
- 또 타입변환 -> 역시 예상한대로 Nan값이 있다는 오류이다. 콤마데이터를 지우고 생긴 Nan값과 비슷하게, 하이픈데이터를 지우고 나니 Nan값이 생긴 것같다.
>>> df['분양가격'] = df['분양가격'].fillna(0)
- 또 확인하니 이번엔 'ValueError: invalid literal for int() with base 10: '' ' 오류이다. 빈값이 어디에 있는지 확인해보자.
>>> df.loc[df['분양가격'] == '']
지역명 규모구분 연도 월 분양가격
3683 광주 전용면적 85㎡초과 102㎡이하 2019 5
3686 대전 전용면적 60㎡이하 2019 5
3688 대전 전용면적 85㎡초과 102㎡이하 2019 5
3690 울산 전체 2019 5
3691 울산 전용면적 60㎡이하 2019 5
3692 울산 전용면적 60㎡초과 85㎡이하 2019 5
3693 울산 전용면적 85㎡초과 102㎡이하 2019 5
3694 울산 전용면적 102㎡초과 2019 5
3696 세종 전용면적 60㎡이하 2019 5
>>> df.loc[df['분양가격']=='', '분양가격'] = 0 # 빈값인 행에서 '분양가격' 컬럼에만 0으로 채운다.
- 다시 해보니...
>>> df['분양가격'].astype(int)
0 5841
1 5652
2 5882
3 5721
4 5879
...
4500 3955
4501 4039
4502 3962
4503 0
4504 3601
Name: 분양가격, Length: 4505, dtype: int32 # int 형으로 바뀌었다!!!!!
★주의할 점★
df.info()를 실행해보면 '분양가격' 컬럼에는 'object'라고 뜬다.
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4505 entries, 0 to 4504
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 지역명 4505 non-null object
1 규모구분 4505 non-null object
2 연도 4505 non-null int64
3 월 4505 non-null int64
4 분양가격 4505 non-null object
dtypes: int64(2), object(3)
memory usage: 176.1+ KB
이유는? df['분양가격'].astype(int)만 해주었기 때문. int형으로 변환해서 df['분양가격']에 넣어줘야한다.
사소한 실수지만 해당 데이터에 저장해주는 것을 습관화해야한다.
- correct : df['분양가격'] = df['분양가격'].astype(int)
4. '규모구분' column에 불필요한 '전용면적' 제거하기.
- value_counts() 메서드로 유일한 값 별로 보면 '전용면적'이란 단어들이 있다. 그 단어를 replace()로 지워보자.
>>> df['규모구분'].value_counts()
전용면적 60㎡이하 901
전용면적 60㎡초과 85㎡이하 901
전용면적 102㎡초과 901
전용면적 85㎡초과 102㎡이하 901
전체 901
Name: 규모구분, dtype: int64
>>> df['규모구분'] = df['규모구분'].str.replace('전용면적', '')
# 다시 확인.
>>> df['규모구분'].value_counts()
85㎡초과 102㎡이하 901
102㎡초과 901
60㎡이하 901
60㎡초과 85㎡이하 901
전체 901
Name: 규모구분, dtype: int64
5. 지역명 별로 평균 분양 가격을 확인한다. (groupby)
- groupby() 메서드는 각 컬럼의 유일한 값 별로 묶어주는 역할을 한다.
- 평균은 mean() 메서드를 이용하는데, 산술 가능한 컬럼에 대해서만 자동적으로 평균을 계산한다.
>>> df.groupby('지역명').mean()
연도 월 분양가격
지역명
강원 2017.45283 6.566038 2339.807547
경기 2017.45283 6.566038 4072.667925
경남 2017.45283 6.566038 2761.275472
경북 2017.45283 6.566038 2432.128302
광주 2017.45283 6.566038 2450.728302
대구 2017.45283 6.566038 3538.920755
대전 2017.45283 6.566038 2479.135849
부산 2017.45283 6.566038 3679.920755
서울 2017.45283 6.566038 7225.762264
세종 2017.45283 6.566038 2815.098113
울산 2017.45283 6.566038 1826.101887
인천 2017.45283 6.566038 3578.433962
전남 2017.45283 6.566038 2270.177358
전북 2017.45283 6.566038 2322.060377
제주 2017.45283 6.566038 2979.407547
충남 2017.45283 6.566038 2388.324528
충북 2017.45283 6.566038 2316.871698
6. 분양 가격이 100보다 작은 행을 제거한다.
- 행을 제거하는 메서드는 df.drop(인덱스, axis=0) 이다.
- 열을 제거할 경우에는 axis옵션을 1로 지정해주면 된다.
# '분양가격'이 100보다 작은 행들을 가져온다. loc 메서드를 이용한다.
>>> df.loc[df['분양가격']<100]
지역명 규모구분 연도 월 분양가격
28 광주 85㎡초과 102㎡이하 2015 10 0
29 광주 102㎡초과 2015 10 0
34 대전 102㎡초과 2015 10 0
81 제주 60㎡이하 2015 10 0
113 광주 85㎡초과 102㎡이하 2015 11 0
... .. ... ... .. ...
4461 세종 60㎡이하 2020 2 0
4488 전남 85㎡초과 102㎡이하 2020 2 0
4493 경북 85㎡초과 102㎡이하 2020 2 0
4499 경남 102㎡초과 2020 2 0
4503 제주 85㎡초과 102㎡이하 2020 2 0
[320 rows x 5 columns]
# 그 행들의 인덱스를 구하려면 index 메서드를 이용한다.
>>> df.loc[df['분양가격']<100].index
Int64Index([ 28, 29, 34, 81, 113, 114, 119, 166, 198, 199,
...
4418, 4448, 4453, 4458, 4459, 4461, 4488, 4493, 4499, 4503],
dtype='int64', length=320)
>>> idx = df.loc[df['분양가격']<100].index
# 인덱스들이 저장되어있는 것을 확인 가능하다.
>>> idx
Int64Index([ 28, 29, 34, 81, 113, 114, 119, 166, 198, 199,
...
4418, 4448, 4453, 4458, 4459, 4461, 4488, 4493, 4499, 4503],
dtype='int64', length=320)
# drop 메서드로 idx에 해당하는 행(axis=0)을 제거한다.
>>> df = df.drop(idx, axis=0)
>>> df.loc[df['분양가격']<100] # 제거한 후, 다시 확인해보면 검색되는 행이 없다.
Empty DataFrame
Columns: [지역명, 규모구분, 연도, 월, 분양가격]
Index: []
# '분양가격'이 4505개에서 4185개로 감소하였다.
>>> df.count()
지역명 4185
규모구분 4185
연도 4185
월 4185
분양가격 4185
dtype: int64
- groupby() 메서드로 묶고, 여러 통계정보를 산출할 수 있다. ex) 평균, 최대 등등
# 지역별 분양가 데이터 갯수를 산출한다.
>>> df.groupby('지역명')['분양가격'].count()
지역명
강원 257
경기 265
경남 260
경북 253
광주 213
대구 256
대전 210
부산 265
서울 265
세종 250
울산 159
인천 261
전남 261
전북 262
제주 230
충남 253
충북 265
Name: 분양가격, dtype: int64
>>> df.groupby('지역명')['분양가격'].max()
지역명
강원 3906
경기 5670
경남 4303
경북 3457
광주 4881
대구 5158
대전 4877
부산 4623
서울 13835
세종 3931
울산 3594
인천 5188
전남 3053
전북 3052
제주 5462
충남 3201
충북 2855
Name: 분양가격, dtype: int32
# 연도별 분양가격의 평균을 산출한다.
>>> df.groupby('연도')['분양가격'].mean()
연도
2015 2788.707819
2016 2934.250000
2017 3143.311795
2018 3326.951034
2019 3693.422149
2020 3853.960526
Name: 분양가격, dtype: float64
- 그룹으로 묶는 것을 한가지 이상도 가능하다. 즉 multi-index가 가능하다.
- 연도별, 규모별 분양가격을 알아보자.
- 먼저 '연도'로 묶고, 그 안에서 '규모구분'으로 다시 묶은 후에 각 그룹의 '분양가격'을 보여주는 것이다.
>>> df.groupby(['연도','규모구분'])['분양가격'].mean()
연도 규모구분
2015 102㎡초과 2980.977778
60㎡이하 2712.583333
60㎡초과 85㎡이하 2694.490196
85㎡초과 102㎡이하 2884.395833
전체 2694.862745
2016 102㎡초과 3148.099476
60㎡이하 2848.144279
60㎡초과 85㎡이하 2816.965686
85㎡초과 102㎡이하 3067.380435
전체 2816.073529
2017 102㎡초과 3427.649746
60㎡이하 3112.538071
60㎡초과 85㎡이하 2981.950980
85㎡초과 102㎡이하 3204.075145
전체 3008.279412
2018 102㎡초과 3468.355932
60㎡이하 3286.184783
60㎡초과 85㎡이하 3227.458128
85㎡초과 102㎡이하 3467.184211
전체 3235.098522
2019 102㎡초과 4039.854839
60㎡이하 3486.910112
60㎡초과 85㎡이하 3538.545918
85㎡초과 102㎡이하 3933.538462
전체 3515.974490
2020 102㎡초과 4187.566667
60㎡이하 3615.968750
60㎡초과 85㎡이하 3594.852941
85㎡초과 102㎡이하 4532.090909
전체 3603.911765
Name: 분양가격, dtype: float64
- 예쁘게 출력하기 위해서 pd.DataFrame으로 한번 더 감싸주면 된다.
>>> pd.DataFrame(df.groupby(['연도','규모구분'])['분양가격'].mean())
분양가격
연도 규모구분
2015 102㎡초과 2980.977778
60㎡이하 2712.583333
60㎡초과 85㎡이하 2694.490196
85㎡초과 102㎡이하 2884.395833
전체 2694.862745
2016 102㎡초과 3148.099476
60㎡이하 2848.144279
60㎡초과 85㎡이하 2816.965686
85㎡초과 102㎡이하 3067.380435
전체 2816.073529
2017 102㎡초과 3427.649746
60㎡이하 3112.538071
60㎡초과 85㎡이하 2981.950980
85㎡초과 102㎡이하 3204.075145
전체 3008.279412
2018 102㎡초과 3468.355932
60㎡이하 3286.184783
60㎡초과 85㎡이하 3227.458128
85㎡초과 102㎡이하 3467.184211
전체 3235.098522
2019 102㎡초과 4039.854839
60㎡이하 3486.910112
60㎡초과 85㎡이하 3538.545918
85㎡초과 102㎡이하 3933.538462
전체 3515.974490
2020 102㎡초과 4187.566667
60㎡이하 3615.968750
60㎡초과 85㎡이하 3594.852941
85㎡초과 102㎡이하 4532.090909
전체 3603.911765
7. 피벗테이블을 활용하여 데이터를 분석한다.
- 피벗테이블이란?
많은 양의 데이터에서 필요한 자료만을 뽑아 새롭게 표를 작성해주는 기능이라고 한다. 피벗 테이블을 사용하게 되면 사용자 임의대로 데이터를 정렬하고 필터링할 수 있다고 한다. 즉, 데이터 처리에 대한 매우 다양한 기능을 제공하는 기법이라고 할 수 있다. 엑셀에서의 pivot table 기능과 유사하다.
위키백과 : 피벗 테이블은 커다란 표의 데이터를 요약하는 통계표이다. 피벗 테이블은 데이터 처리의 한 기법이다. 유용한 정보에 집중할 수 있도록 하기 위해 통계를 정렬 또는 재정렬한다.
- 보통 pivot_table에서 쓰이는 옵션으로
index는 행인덱스, columns는 열인덱스, values는 값을 의미한다.
- 행은 '연도', 열은 '규모구분', 값은 '분양가격'을 나타내는 피봇테이블을 나타내보자.
# 행인덱스(index) : 연도
# 열인덱스(columns) : 규모구분
# 값(values) : 분양가격
>>> pd.pivot_table(df, index='연도', columns='규모구분', values='분양가격')
규모구분 102㎡초과 60㎡이하 60㎡초과 85㎡이하 85㎡초과 102㎡이하
전체
연도
2015 2980.977778 2712.583333 2694.490196 2884.395833 2694.862745
2016 3148.099476 2848.144279 2816.965686 3067.380435 2816.073529
2017 3427.649746 3112.538071 2981.950980 3204.075145 3008.279412
2018 3468.355932 3286.184783 3227.458128 3467.184211 3235.098522
2019 4039.854839 3486.910112 3538.545918 3933.538462 3515.974490
2020 4187.566667 3615.968750 3594.852941 4532.090909 3603.911765
이렇게 데이터 전처리하는 방법에 대해 알아보았다. 이제 데이터를 전처리한 후 시각화로 보여주게 되는데 그때 필요한 시각화 도구 matplotlib 모듈에 대해 다음 글에서 알아보겠다.
'Python' 카테고리의 다른 글
[colab] 6. 머신러닝 (0) | 2021.01.23 |
---|---|
[colab] 5. colab으로 matplotlib를 이용한 그래프 그리기 (0) | 2021.01.22 |
[colab] 3. colab으로 pandas 모듈 사용하기(2) (0) | 2021.01.21 |
[colab] 3. colab으로 pandas 모듈 사용하기(1) (0) | 2021.01.20 |
[colab] 2. colab으로 Numpy 모듈 사용하기 (0) | 2021.01.19 |