서론
이번 시간에는 현재 진행중인 미국 내 통계사의 산업통계 데이터를 통해
2022년 이후의 카운티 별(county) 초소형 기업(Microbusiness) 현황에 대해 간단한 분석을 해보고,
필자 나름대로의 데이터 전처리를 진행하려 합니다.
*county : 자치주, 자치군 등 영미권의 행정 구역을 뜻합니다.
(예컨대 미 주소에서 체로키 카운티, 달라스 카운티 등이 존재)
데이터셋의 목표이자 컴피티션의 목표는 북미 통계청에서 제공한 census 통계자료를 확인해보고,
이를 feature로 판단하여 train 세트에서 확인한 후 미래의 기업밀도를 예측하는 것입니다.
# 목표 설정
# 북미의 지역구 별 초소형 기업의 밀도 분석
# 타겟(종속변수) : microbuisiness_density
# 피쳐 : 밀도를 계산하기 위한 독립변수인데, 먼저 cifps는 소도시 별 "코드"를 뜻합니다.
필자가 분석한 feature는 다음과 같습니다.
# 핵심 피쳐 선정
※ 현재 진행중인 컴피티션이기 때문에 원본 전체는 미공개하고, 데이터프레임의 originality는
최대한 가리는 방향으로 전개했습니다.
※ 실제 분석결과는 아직 진행중으로, 결과값이 나오면 다시 수정하거나, 추가 게시물로 서술할 예정입니다.
※ 현재 영미권에서 쓰이는 용어와 번역상의 차이가 존재할 수 있습니다.
허나 이 부분은 최대한 국내 정서와 행정 명칭과 유사하게 번역하고자 노력했습니다.
시작
# 알고리즘 선정
종속 변수는 특정한 지역구 안에 존재하는 기업 밀도(영세한 기업)이기 때문에
사용 알고리즘은 간단하게 Linear regression과 KNN(k 최근접 모델), boosting 계열에서는
LightGbm을 사용하려 합니다.
추후 제시하겠지만, 가계소득값으로 인해 데이터가 차이가 커졌는데요.
자릿수가 만 단위가 넘어가는 금액 vs 십의 자릿수 이하의 percent가 있기 때문에
표준화 스케일로 스케일링도 진행할 예정입니다.
train = pd.read_csv('train.csv') # 훈련용 데이터
census = pd.read_csv('census_starter.csv') # 독립변수에 대한 설명
train은 학습용 데이터가 있는 csv였습니다.
현재 cfips가 각 자치구 별 코드를 의미하는 것으로, county는 제거하고 묶으려 합니다.
train.head()
census는 컴피티션 담당자가 참고사항으로 업로드한 파일로, 최근 5년간의 카운티의
인구 흐름이 담겨 있습니다. #핵심 피쳐 선정에서 미리 서술한 바 있습니다.
census.head()
train.info()
# train은 cfips가 x년x월별로 구분, census는 cfips가 연도 구분없이 존재함
# trian의 cfips를 총 기간동안 데이터의 평균으로 groupby할 것인지 vs 가장 최근 날짜의 데이터만 남길것인지
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14677 entries, 0 to 14676
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 row_id 14677 non-null object
1 cfips 14677 non-null int64
2 county 14677 non-null object
3 state 14677 non-null object
4 first_day_of_month 14677 non-null object
5 microbusiness_density 14676 non-null float64
6 active 14676 non-null float64
dtypes: float64(2), int64(1), object(4)
memory usage: 802.8+ KB
지역코드가 train에서는 2000년 1월 1일~2010년 1월 1일과 같은 방식으로
연속적으로 되어 있었으나, census에서는 단 하나씩만 존재했습니다.
이때문에 groupby로 전부 묶을지, 혹은 가장 최신 연도만 빼고 모두 지울지 고민했습니다.
현재 공유되어 있는 코드에서는 시계열별로 분석하는 경우가 많았으나,
도메인 지식이 완벽하지 않은 상태에서는 너무 많은 흐름까지 고려하기 어려울 것이라고 판단하여
나머지는 제거하려고 계획했습니다.
census.describe(include='all') # 가장 최근의 통계치가 영향이 있을 것이라고 판단하여 cfips는 가장 최근 날짜의 데이터로 남기도록 함
# 그러나 그 자체만으로 의미도 있을 것
nan값을 확인했습니다만, 많진 않았습니다.
# 결측치부터 확인
census.isnull().sum().sort_values(ascending=True)
pct_bb_2017 0
median_hh_inc_2019 0
median_hh_inc_2017 0
pct_it_workers_2019 0
pct_it_workers_2017 0
pct_foreign_born_2019 0
pct_foreign_born_2017 0
pct_college_2019 0
pct_foreign_born_2018 0
pct_bb_2018 0
pct_college_2017 0
cfips 0
pct_bb_2019 0
pct_college_2018 0
pct_college_2020 1
median_hh_inc_2018 1
pct_it_workers_2021 1
pct_it_workers_2020 1
pct_it_workers_2018 1
pct_bb_2021 1
pct_foreign_born_2021 1
pct_foreign_born_2020 1
pct_college_2021 1
pct_bb_2020 1
median_hh_inc_2020 2
median_hh_inc_2021 2
dtype: int64
위와 아래 내용이 필자가 originality를 최대한 보전하려고 한 이유입니다.
공적인 기관이나, 주최측에서 상당히 엄밀한 조사가 있었는지 결측치가 정말 없습니다.
혹은, 이미 오류검사를 한번 끝내고 업로드했을 가능성도 있습니다.
train.isnull().mean() # 결측치 없음
row_id 0.000000
cfips 0.000000
county 0.000000
state 0.000000
first_day_of_month 0.000000
microbusiness_density 0.000068
active 0.000068
dtype: float64
참고자료의 결측치는 → 평균값으로 처리했습니다.
census.pct_bb_2020 = census.pct_bb_2020.fillna(census.pct_bb_2020.mean())
census.pct_bb_2021 = census.pct_bb_2021.fillna(census.pct_bb_2021.mean())
census.pct_college_2020 = census.pct_college_2020.fillna(census.pct_college_2020.mean())
census.pct_college_2021 = census.pct_college_2021.fillna(census.pct_college_2021.mean())
census.pct_foreign_born_2020 = census.pct_foreign_born_2020.fillna(census.pct_foreign_born_2020.mean())
census.pct_foreign_born_2021 = census.pct_foreign_born_2021.fillna(census.pct_foreign_born_2021.mean())
census.pct_it_workers_2018 = census.pct_it_workers_2018.fillna(census.pct_it_workers_2018.mean())
census.pct_it_workers_2020 = census.pct_it_workers_2020.fillna(census.pct_it_workers_2020.mean())
census.pct_it_workers_2021 = census.pct_it_workers_2021.fillna(census.pct_it_workers_2021.mean())
census.median_hh_inc_2020 = census.median_hh_inc_2020.fillna(census.median_hh_inc_2020.mean())
census.median_hh_inc_2018 = census.median_hh_inc_2018.fillna(census.median_hh_inc_2018.mean())
census.median_hh_inc_2021 = census.median_hh_inc_2021.fillna(census.median_hh_inc_2021.mean)
제거 확인
census.isnull().sum() # 결측치 제거
pct_bb_2017 0
pct_bb_2018 0
pct_bb_2019 0
pct_bb_2020 0
pct_bb_2021 0
cfips 0
pct_college_2017 0
pct_college_2018 0
pct_college_2019 0
pct_college_2020 0
pct_college_2021 0
pct_foreign_born_2017 0
pct_foreign_born_2018 0
pct_foreign_born_2019 0
pct_foreign_born_2020 0
pct_foreign_born_2021 0
pct_it_workers_2017 0
pct_it_workers_2018 0
pct_it_workers_2019 0
pct_it_workers_2020 0
pct_it_workers_2021 0
median_hh_inc_2017 0
median_hh_inc_2018 0
median_hh_inc_2019 0
median_hh_inc_2020 0
median_hh_inc_2021 0
dtype: int64
언급했듯이, state자체는 일대일 대응하는 숫자형 변수가 있기 때문에
제거 예정입니다.
# object 확인
train.info() # cfips가 있기 때문에 county, state는 삭제해도 무방
# first_day_of_month는 날짜로 변환
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14677 entries, 0 to 14676
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 row_id 14677 non-null object
1 cfips 14677 non-null int64
2 county 14677 non-null object
3 state 14677 non-null object
4 first_day_of_month 14677 non-null object
5 microbusiness_density 14676 non-null float64
6 active 14676 non-null float64
dtypes: float64(2), int64(1), object(4)
memory usage: 802.8+ KB
복구는 파일을 바로 로드하면 되서 매우 위험한 것은 아니지만
무의식적으로 그냥 실행할 때를 고려해서 삭제에는 항상 주의를 표시했습니다.
# 열 삭제
# 주의
train.drop(labels = ['county','state'], axis=1, inplace =True)
train.head()
train.first_day_of_month
0 2019-08-01
1 2019-09-01
2 2019-10-01
3 2019-11-01
4 2019-12-01
...
14672 2020-04-01
14673 2020-05-01
14674 2020-06-01
14675 2020-07-01
14676 20
Name: first_day_of_month, Length: 14677, dtype: object
지역 코드를 가장 앞으로 오도록, 열의 순서를 통일했습니다.
참고사항으로, 지역 코드(cfips)는 일반적인 북미의 FIPS라는 지역코드와
카운티별 고유 숫자를 합친 것입니다. (4~5자리)
col1=census.columns[5:].to_list()
col2=census.columns[:5].to_list()
new_col=col1+col2
census=census[new_col]
census
순서 바뀐것 확인
census.columns
Index(['cfips', 'pct_college_2017', 'pct_college_2018', 'pct_college_2019',
'pct_college_2020', 'pct_college_2021', 'pct_foreign_born_2017',
'pct_foreign_born_2018', 'pct_foreign_born_2019',
'pct_foreign_born_2020', 'pct_foreign_born_2021', 'pct_it_workers_2017',
'pct_it_workers_2018', 'pct_it_workers_2019', 'pct_it_workers_2020',
'pct_it_workers_2021', 'median_hh_inc_2017', 'median_hh_inc_2018',
'median_hh_inc_2019', 'median_hh_inc_2020', 'median_hh_inc_2021',
'pct_bb_2017', 'pct_bb_2018', 'pct_bb_2019', 'pct_bb_2020',
'pct_bb_2021'],
dtype='object')
train 데이터프레임도 열순서를 바꾸겠습니다.
col1=train.columns[2:].to_list()
col2=train.columns[:2].to_list()
new_col=col1+col2
train=train[new_col]
train
안전성을 높이기 위해서 복사합니다.
train2 = train.copy()#복사
train2
아까 있었던 날짜데이터, first_day_of_month가 있었죠?
datetime 형식으로 바꿔줍니다
new_train["first_day_of_month"] = pd.to_datetime(new_train["first_day_of_month"]) # first_day_of_month 날짜형 데이터로 처리
현재 census 데이터프레임이 cfips를 인덱스로 하여 정렬되어
존재하고 있었습니다.
train 데이터프레임도 기준을 맞춰주기 위해, 정렬해줍니다.
new_train = train.iloc[:,:5].sort_values(by='cfips')
new_train
※참조. 현재 census 상태
자세히 보면 cfips별 행의 갯수가 3142 - 3135개로, 서로 데이터프레임이
7개 정도 차이가 납니다. 이 부분은 통계청에서 조사한 county 수와
주최측이 조사한 county수가 차이가 났던것으로 판단했습니다.
갯수의 차이는 merge로 묶어서 조정해줍니다.
the_new_train = pd.merge(new_train, census, left_index=True, right_index=True, how='left') #1 조합 성공
the_new_train
좋습니다. 이제 필자가 필요한 변수들은 모두 한손안에 들어왔습니다.
여기서 흥미로운 인사이트를 제시하려 합니다.
현재까지 도시의 대졸자 비율, 해외 입주자 비율, 가계소득 중앙값, 인터넷 가입자 비율,
IT업계 종사자들을 독립변수로 결정하고 있었습니다.
그렇다면, 이 다섯가지 변수 중 기업의 밀도에 높은 상관관계를 갖고 있는 것은
어떤 것일까요? 대졸자 비율일까요? 도시의 가계소득 일까요?
대형 그래프를 그려봤습니다.
plt.figure(figsize = (18,18))
sns.heatmap(the_new_train.head(10).corr(), vmax = 1, vmin = -1, annot = True)
가장 왼쪽열(microbusiness_density)를 주목해봅니다.
active는 현재 활동중인 기업 수이기 때문에, 특별히 주목할 필요는 없습니다.
절대값 1, 즉 1이나 -1에 가까운 변수들이 높은 상관관계를 갖게 되는데요,
이부분에서는 대졸자 비율(pct_college), 가계소득 중앙값(median_hh_inc) 등이
상대적으로 높은 관계성을 갖고 있다고 추론할 수 있습니다.
다만 대졸자 비율이나 가계소득이 높다고 해서, 무조건 초소형 기업의 수가
높다고 볼수는 없습니다. 강한 상관관계가 인과관계로 연역된다고 볼 수 없기 때문입니다.
# cfips 를 기준으로 나머지는 추후 test에서 데이터 전처리
자, 이제 전처리를 끝내려고 했으나 한가지 문제점이 있습니다.
the_new_train.info() # object발견. 2021의 가계소득 중앙값
# the_new_train.median_hh_inc_2021.dtype
the_new_train['median_hh_inc_2021']
<class 'pandas.core.frame.DataFrame'>
Int64Index: 3135 entries, 1001 to 56045
Data columns (total 29 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 microbusiness_density 3135 non-null float64
1 active 3135 non-null int64
2 row_id 3135 non-null object
3 first_day_of_month 3135 non-null datetime64[ns]
4 pct_college_2017 3135 non-null float64
5 pct_college_2018 3135 non-null float64
6 pct_college_2019 3135 non-null float64
7 pct_college_2020 3135 non-null float64
8 pct_college_2021 3135 non-null float64
9 pct_foreign_born_2017 3135 non-null float64
10 pct_foreign_born_2018 3135 non-null float64
11 pct_foreign_born_2019 3135 non-null float64
12 pct_foreign_born_2020 3135 non-null float64
13 pct_foreign_born_2021 3135 non-null float64
14 pct_it_workers_2017 3135 non-null float64
15 pct_it_workers_2018 3135 non-null float64
16 pct_it_workers_2019 3135 non-null float64
17 pct_it_workers_2020 3135 non-null float64
18 pct_it_workers_2021 3135 non-null float64
19 median_hh_inc_2017 3135 non-null int64
20 median_hh_inc_2018 3135 non-null float64
21 median_hh_inc_2019 3135 non-null int64
22 median_hh_inc_2020 3135 non-null float64
23 median_hh_inc_2021 3135 non-null object
24 pct_bb_2017 3135 non-null float64
25 pct_bb_2018 3135 non-null float64
26 pct_bb_2019 3135 non-null float64
27 pct_bb_2020 3135 non-null float64
28 pct_bb_2021 3135 non-null float64
dtypes: datetime64[ns](1), float64(23), int64(3), object(2)
memory usage: 799.3+ KB
cfips
1001 62660.0
1003 64346.0
1005 36422.0
1007 54277.0
1009 52830.0
...
56037 76668.0
56039 94498.0
56041 75106.0
56043 62271.0
56045 65566.0
Name: median_hh_inc_2021, Length: 3135, dtype: object
한가지 object로 되어있는 변수가 있습니다.
이 피쳐는 오류가 섞여 있는것으로 판단했습니다.
분명 숫자로만 구성되어 있는데 object에서 int로 치환이 안됩니다.
the_new_train = the_new_train.astype({"median_hh_inc_2021":"int"})
t = the_new_train.iloc[:,23:24]
# the_new_train['median_hh_inc2021'] = the_new_train['median_hh_inc_2021']
# the_new_train.info() # median_hh_inc_2021는 어째서인지 object에서 변경이 안됨.
# 직접 보기에는 숫자들인데, method로 확인됨
#더미데이터로 제작은 열이 3000개로 늘어나서 불가
TypeError Traceback (most recent call last)
<ipython-input-88-1d83a56d7ff4> in <module>
----> 1 the_new_train = the_new_train.astype({"median_hh_inc_2021":"int"})
2 t = the_new_train.iloc[:,23:24]
3 # the_new_train['median_hh_inc2021'] = the_new_train['median_hh_inc_2021']
4 # the_new_train.info() # median_hh_inc_2021는 어째서인지 object에서 변경이 안됨.
5 # 직접 보기에는 숫자들인데, method로
8 frames
/usr/local/lib/python3.8/dist-packages/pandas/_libs/lib.pyx in pandas._libs.lib.astype_intsafe()
TypeError: int() argument must be a string, a bytes-like object or a number, not 'method'
# 열 삭제
# 주의
the_new_train.drop('median_hh_inc_2021', axis = 1, inplace = True)
the_new_train.isnull().sum()
microbusiness_density 0
active 0
row_id 0
first_day_of_month 0
pct_college_2017 0
pct_college_2018 0
pct_college_2019 0
pct_college_2020 0
pct_college_2021 0
pct_foreign_born_2017 0
pct_foreign_born_2018 0
pct_foreign_born_2019 0
pct_foreign_born_2020 0
pct_foreign_born_2021 0
pct_it_workers_2017 0
pct_it_workers_2018 0
pct_it_workers_2019 0
pct_it_workers_2020 0
pct_it_workers_2021 0
median_hh_inc_2017 0
median_hh_inc_2018 0
median_hh_inc_2019 0
median_hh_inc_2020 0
pct_bb_2017 0
pct_bb_2018 0
pct_bb_2019 0
pct_bb_2020 0
pct_bb_2021 0
dtype: int64
# row_id drop한 버전
the_new_train.drop('row_id', axis = 1, inplace = True)
이제 훈련셋을 분리하여 모델링할 예정입니다.
# 훈련셋 분리 후 스케일링 →학습 → 시작
X = the_new_train.drop(['microbusiness_density', 'first_day_of_month'], axis = 1)
y = the_new_train['microbusiness_density']
# 분리. 날짜 제외
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 10)
독립변수 확인했습니다.
X # 확인
Standard 스케일링을 진행합니다.
scale = StandardScaler() # 에러 위험을 낮추기 위해 고려
X_trainst = scale.fit_transform(X_train)
X_testst = scale.transform(X_test)
이제 선형회귀 모델을 적용합니다.
LinearRegression은 초기에 패키지로 다 호출했습니다.
model = LinearRegression()
model.fit(X_trainst, y_train)
LinearRegression()
예측값도 구합니다.
pred = model.predict(X_testst)
필자는 점수를 계산하기 전에, 일단 추상적으로 떠올리던 것을
실체화해서, 시각적으로 구현하는 것을 선호합니다.
눈으로 보이면 무엇이 안된것인지, 무엇이 잘된것인지 더 잘보입니다.
그 예측값이 완전히 틀린 것이라고 하더라도요!
# 테이블로 평가
comparison = pd.DataFrame({
'actual': y_test, # 실제값
'pred': pred
})
comparison
actual pred
cfips
8043 3.623784 3.407006
53013 2.580258 7.674203
51017 2.736318 1.896151
26157 2.078809 2.228772
26119 2.754960 1.227988
... ... ...
26127 2.666148 2.870470
55095 4.640566 3.946307
37109 5.263868 4.783580
29159 2.219227 3.034277
55101 3.818579 4.896052
627 rows × 2 columns
# scatterplot 제작
plt.figure(figsize=(10, 7))
sns.scatterplot(x = 'actual', y= 'pred', data = comparison)
plt.show() # 1,2개의 아웃라이어
안타깝게도 아웃라이어가 남아있습니다.
두 세개정도 완전히 커진 수치값이 보이네요.
평가 점수로 넘어갑니다.
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
# mse 평가
mean_squared_error(y_test, pred)
10.444502825135869
스크롤이 너무 길어져서, mse, mae를 다시 호출했습니다.
# rmse 평가
mean_squared_error(y_test, pred, squared=False)
3.2317956038610904
# mae 평가
mean_absolute_error(y_test, pred)
1.6267654123898432
결정계수도 확인해봅니다.
model.score(X_trainst, y_train) # R²
# 매우 낮은 결정계수
0.19693808002268087
변동이 강해서인지, 뚜렷하게 높은 점수를 보이지는 않게 되었습니다.
이렇게 해서, 첫번째로 데이터 분석과 전처리 과정을 종료하겠습니다.
선형회귀의 식과 정확도 평가를 진행했고, 다음 시간에는
KNN 모델과 점수를 높여보기 위한 LightGBM 알고리즘을 진행하며,
추후 파일 업로드까지 예정되어 있습니다.
끝.😎
'머신러닝 > 지도학습' 카테고리의 다른 글
[Feature engineering] 데이터 처리 사례_2021 서울시 농산물 가격 분석_1 (0) | 2023.01.01 |
---|---|
[Machine Learning] Kaggle_데이터셋 분석_미 초소형 기업 밀도 예측_ep.2 (0) | 2022.12.31 |
[Machine Learning] Kaggle_데이터셋 분석_Video_Games_Sales.1 (0) | 2022.12.29 |
[Machine Learning] Kaggle_연습사례 분석_Spaceship_titanic_2 (0) | 2022.12.28 |
[Machine Learning] Kaggle_연습사례 분석_Spaceship_titanic (2) | 2022.12.27 |