이번 테스트는 박물관데이터를 전처리하는 과제인데 난이도 별 3개 답게 평소보다 8시에 시작해서 4시에 4-2번까지 풀고 잠들었다....
Json 데이터를 데이터프레임으로 만드는 문제인데 json의 크기를 찍어보니 2개의 리스트를 가지고있다는걸 확인했고 구글링 결과 정보가 들어있는 부분만 pd.DataFrame으로 처리해주면 된다는 결론이 나와 후딱 처리하였습니다.
info()로 데이터를 찍을때 ''으로 되어있으면 정상적인 데이터로 인식하기때문에 이를 방지하고자 ''로 되어있는 데이터를 null값으로 표현 하라는 문제였다.
df_target = df_target.replace('', np.nan)
replace기능으로 빠르게 해결해 주었다.
문제에서 주어진 관람료 컬럼을 int로 위도, 경도 데이터를 float으로 바꿔주라는 문제인데 astype 기능으로 빠르게 끝낼줄 알았으나 astype 기능은 null값이 있으면 동작하지 않는 문제가 있어 fillna로 null값에 0을 채운다음에 astype으로 타입을 변경해 주었습니다.
사용할일 없는 컬럼을 지워주는 문제인데 drop을 사용하여 지워주었습니다.
관람료 컬럼들중에서 10만원이 넘거나 10원 단위로 나눠지지않는 값들은 이상치라고 생각하고 정리하는 문제였습니다.
df3[type_int_col] = df3[type_int_col][df3[type_int_col] % 10 == 0]
df3[type_int_col] = df3[type_int_col][df3[type_int_col] <= 100000]
df3 = df3.dropna(axis=0)
df3[type_int_col] = df3[type_int_col].astype(int)
데이터프레임의 일부분에 조건을 부여해서 찾는건 익숙하나 위 처럼 복잡하게 기술해야해서 햇갈린 문제였던거 같고 10단위로 나누어지는 지 확인하기위해 나머지를 확인하는 과정에서 데이터타입이 실수로 바뀌어 정수로 바꾸는 과정을 수행하였습니다.
시설명 컬럼에 휴관을 포함하면 제거해주고 중복 데이터를 삭제하는데 띄어쓰기되어있는것도 고려하라는 문제였습니다. 문제안의 문제 우선 코드리뷰하면서 학습한 차집합 기능을 사용하여 휴관을 포함한 데이터를 제거하는것 부터 진행하였고
# 조건1
# 교집합 : A[A.isin(B)] / 차집합 : A[~A.isin(B)]
df4 = df4[~df4['시설명'].str.contains('휴관')]
띄어쓰기를 주의하기위해 repalce로 띄어쓰기를 제거한 뒤 최신데이터 기준으로 정렬한뒤 drop_duplicates로 중복행을 제거하였습니다. 여기서 keep이라는 파라메터를 사용하였는데 최신데이터 기준으로 맞추기위해 사용하였습니다.
# 조건 2,3, 4
df4['시설명'] = df4['시설명'].str.replace(' ','')
df4 = df4.sort_values(by='데이터기준일자', ascending=False) # 시설명 열을 기준으로 내림차순 정렬
df4 = df4.drop_duplicates(subset='시설명', keep='first') # 중복 제거, 마지막으로 나타난 행 남기기
df4 = df4.sort_index()
시간데이터를 사용하여 관람 가능시간 컬럼을 만드는 문제인데 이번 과제에서 3번째로 시간을 많이쓴 파트같다....
우선 시간데이터를 사용하기위해서는 pd.to_datetime를 사용하여 시간타입으로 바꿔주어야한다.
# 시간 형식을 datetime으로 변환
x1 = pd.to_datetime(df5['평일관람시작시각'], format='%H:%M')
x2 = pd.to_datetime(df5['평일관람종료시각'], format='%H:%M')
y1 = pd.to_datetime(df5['공휴일관람시작시각'], format='%H:%M')
y2 = pd.to_datetime(df5['공휴일관람종료시각'], format='%H:%M')
이후 이것을 소수점 시간 형식으로 표시하기위해 total_seconds로 초단위로 바꿔주고 3600으로 나눠 소숫점 시간형식으로 표시하고 조건대로 소숫점 2번째자리에서 반올림 해주었습니다.
조건 3번에서 23시간이상 운영하면 24시간으로 표시하라는 조건이 있어 이또한 처리해주었습니다.
# 조건 1, 2, 3, 4
df5['평일관람가능시간'] = round((x2 - x1).dt.total_seconds() / 3600,2)
df5['공휴일관람가능시간'] = round((y2 - y1).dt.total_seconds() / 3600, 2)
df5.loc[df5['평일관람가능시간'] > 23, '평일관람가능시간'] = 24
df5.loc[df5['공휴일관람가능시간'] > 23, '공휴일관람가능시간'] = 24
그런데 새벽까지 전시하는경우 24시간을 더해줘야하는 조건이 있어
# 조건 3 보완
df5['평일관람가능시간'] = df5['평일관람가능시간'].apply(lambda x: x + 24 if x < 0 else x)
df5['공휴일관람가능시간'] = df5['공휴일관람가능시간'].apply(lambda x: x + 24 if x < 0 else x)
다음과 같이 보완하였습니다.
그렇게 어려운 문제는 아닌거 같지만 조건이 너무 많아 오래걸린 문제였다...
소재지도로명주소에 있는 주소를 잘라 광역, 기초, 상세 라는 컬럼에 각각 넣어주는 문제인데 세종시는 이름이 바뀌어 '세종특별자치시'로 바꿔주라는 조건이 달려있습니다.
우선 소재지도로명주소 컬럼 값을 공백기준으로 잘라 리스트로 표현 한것을 address라는 함수에 넣어
address = df6['소재지도로명주소'].str.split(' ')
첫자리는 광역 두번째 자리는 기초 나머지는 상세에 넣도록 코드를 작성하였고 위에서 언급된 '세종특별시' 데이터를 만났을때만 if문으로 처리하게 하였습니다.
# 조건 1 광역
for idx, row in df6.iterrows():
if df6['소재지도로명주소'][idx].startswith('세종특별시'):
df6.at[idx, '광역'] = '세종특별자치시'
elif df6['소재지도로명주소'][idx].startswith('세종특별자차시'):
df6.at[idx, '광역'] = '세종특별자치시'
else:
df6.at[idx, '광역'] = address[idx][0]
# 조건 2 기초
for idx, row in df6.iterrows():
if df6['소재지도로명주소'][idx].startswith('세종특별자치시'):
df6.at[idx, '기초'] = None
elif df6['소재지도로명주소'][idx].startswith('세종특별시'):
df6.at[idx, '기초'] = None
else:
df6.at[idx, '기초'] = address[idx][1]
세종시는 기초가 없어 None값으로 처리함!!!
# 조건 3 상세
result = ' '.join(address[1][2:])
result1 = ' '.join(address[1][1:])
for idx, row in df6.iterrows():
if df6['소재지도로명주소'][idx].startswith('세종특별자치시'):
df6.at[idx, '상세'] = result1
elif df6['소재지도로명주소'][idx].startswith('세종특별시'):
df6.at[idx, '상세'] = result1
else:
result = ' '.join(address[idx][2:])
df6.at[idx, '상세'] = result
그런데
address[1601][1:]
['국세청로', '8-14 ']
이 녀셕은 끝에 ' '공백 한자리를 포함하고있어 아래와 같이 앞뒤 공백을 지워준 뒤 위의 코드를 실행하였다.
for idx, row in df6.iterrows():
df6.at[idx, '소재지도로명주소'] = df6.at[idx, '소재지도로명주소'].strip()
8시쯤 시작했는데 4단계에 도착하니 시간이 새벽 2시....
지금 까지 정리한 광역에 있는 박물관 수의 합계를 나타내는 문제인데 정렬기준을 따로 준비한 딕셔너리를 기준으로 하라는 문제였다.
df = df.groupby('광역')['광역'].count()
df2 = pd.DataFrame(df)
광역을 기준으로 박물관 수를 구했고
df3 = df2.sort_index(key=lambda x: x.map(province_dict))
df3 = df3.rename(columns={'광역': '박물관미술관수'})
map기능을 사용하여 딕셔너리 기준으로 정렬하였다.
안나오면 섭섭한 멀티인덱스 등장...!!
광역과 기초를 기준으로 8개의 박물관을 가지고있는곳을 구하는 문제이다. 딱 읽었을 때는 4-1과 그렇게 차이가 없어 보여 금방 해결한거 같았으나 계속 해서 오류가 생긴 문제였다.....
이 문제는 세종시라는 컬럼에 기초 값이 없었다는걸 깨닭는 게 핵심이다. 왜냐하면
# 조건1
df4 = df4.groupby(['광역', '기초'])['광역'].count()
df5 = pd.DataFrame(df4)
다음과 같이 집계하여도 세종시는 기초값이 None값이라 만들어지지 않기 때문이다....
이 문제를 해결하기위해서는 None으로 되어있는 부분에 fillna로 아무 값이나 채우고나서 진행해야한다.
df4['기초'] = df4['기초'].fillna('세종')
그렇게하면 다음과 같이 세종시를 포함한 값까지 구할수있게되어 조건하나를 만족할수있게된다.
그런데... 정렬을 4-1에서 주어진 조건과 마찬가지로 하라는 조건이 주어져 멀티인덱스를 정렬해야하는데 중요한걸 배운거 같다...
멀티인덱스를 정렬할 때는 정렬시키는 sort_index를 사용하는 순서도 중요한거 같다. 높은 레벨부터 정렬하고나서 낮은 레벨을 정렬하지 않으면 계속해서 원하는대로 정렬되지않은 오류가 생긴다....
전체 광역 중에 박물관미술관구분 기준으로 분류한뒤 어린이와 어른의 관람료 평균의 차가 가장 적은 컬럼과 큰 컬럼을 구하는 문제이다.
우선 무료 관람인 컬럼을 제외하라는 조건을 만족 시켜준뒤
# 조건 1
filter_adult = df8[df8['어른관람료'] != 0]
filter_child = df8[df8['어린이관람료'] != 0]
어린이, 어른의 관람료 평균을 구해주었습니다.
adult = filter_adult.groupby(['광역', '박물관미술관구분'])['어른관람료'].mean()
child = filter_child.groupby(['광역', '박물관미술관구분'])['어린이관람료'].mean()
df9 = pd.DataFrame(adult)
df10 = pd.DataFrame(child)
이후 concat으로 이 두개를 합쳐주고
df11 = pd.concat([df9, df10], axis=1)
관람료 차이 컬럼을 정의 해 주었습니다.
df11['관람료차이'] = df11['어른관람료'] - df11['어린이관람료']
이제 관람료 차이의 최대 최소를 구하기위해 max, min으로 각각 구해줍니다.
max = df11[df11['관람료차이'] == df11['관람료차이'].max()]
min = df11[df11['관람료차이'] == df11['관람료차이'].min()]
마찬가지로 concat으로 합쳐주고 정렬해주었습니다.
df12 = pd.concat([max, min], axis=0)
df12 = df12.sort_index(level='광역', key=lambda x: x.map(province_dict))'
df12 = df12.round(-1).astype(int)
마지막으로 반올림후 정수화까지
구한 데이터프레임중에서
어른 2명, 청소년 1명, 어린이 1명이 가도 2만원 이하
제주도에 있음
박물관 명에 '미술관' 또는 '갤러리' 또는 '아트'가 포함
공휴일에 4시간이상 관람가능
이라는 조건을 만족한 데이터를 구하는 문제입니다.
budget = 20000
# 조건 1
test = ad[ad['어른관람료'] * 2 + ad['청소년관람료'] + ad['어린이관람료'] <= budget]
test.info()
# 조건 2 -1
test = test[test['기초'] == '제주시']
# 조건 2 - 2
test = test[test['시설명'].str.contains('미술관|갤러리|아트')]
test = test[test['공휴일관람가능시간'] >= 4]