언어/Python

[colab] 4. colab으로 데이터 전처리 실습하기

뭉지(moonz) 2021. 1. 22. 16:00
반응형

오늘은 공공데이터 사이트에서 가져온 '부동산 데이터'를 이용해서 전처리하는 실습을 진행하겠다.

처음에는 무조건 필요한 모듈을 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 모듈에 대해 다음 글에서 알아보겠다.

반응형