Data 원본 출처
Target Data(Json): 전국박물관미술관정보표준데이터(공공데이터포털)
import json
# 채점을 위한 코드입니다. 반드시 실행해주세요.
from grading import *
with open('C:/Users/min99/EDA 5/EDA_Level_Test_05 (배포용)/datas/전국박물관미술관정보표준데이터.json', 'r', encoding='utf-8') as f:
json_data = json.load(f)
문제 시작!
1단계: Json Data로 DataFrame으로 만들기
문제 1-1) Json Data로 Pandas DataFrame 만들기 (10점)
- 위에서 읽은 json_data는 아래와 같이 구성되어있습니다. 이를 참고하여 pandas dataframe으로 불러오세요.
- json_data
- json_data['fields']: List. 각 variable은 하나의 Column(열)을 의미하며, {id: Column(열)명}의 형식으로 구성 되어있습니다.
- json_data['records']: List. 각 variable은 하나의 Row(행)을 의미하며, {Column(열)명: data}의 형식으로 구성 되어있습니다.
- json_data
- 조건1: 위의 내용을 참고하여 'json_data'를 이용하여 Pandas DataFrame을 만드세요
- 조건2: json data의 Index와 Column(열)의 순서(order)는 변경하지 마세요. - 앞에서부터 순차적으로 차례로 읽어 그대로 DataFrame으로 만드세요.
- 조건3: 'df_target' 변수에 결과 DataFrame을 할당하세요.
# 1-1
import pandas as pd
dict_values = [list(i.values()) for i in json_data['fields']]
columns = []
for i in range(len(dict_values)):
columns.append(dict_values[i][0])
dict_row_values = [json_data['records'][i].values() for i in range(len(json_data['records']))]
df_target = pd.DataFrame(data=dict_row_values, columns=columns)
df_target.head(2)
check_01_01(df_target)
저는 우선 해당 field에 있는 값들은 딕셔너리로 구성 되어 있으며 이를 따로 빼기위해 values()를 사용하였으며 여기서 values()만 사용하면 그냥 값만 빠지지 않고 타입이 dict_values로 뽑아내지는 것이 확인되어 각각의 값을 리스트에 하나씩 넣은 다음(이중 리스트가 된다.)에 또 for문을 사용하여 각각의 값들을 빼주었습니다. (매우 비효율적인 듯 합니다. 더 좋은 방법이 있을 것 같습니다.)
2단계: DataFrame 전처리 01 - 기초
문제 2-1) 기초 전처리 01 (5점)¶
- 1단계의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- 해당 json_data의 null값은 ""로 구성되어 df_target.info() 또는 df_target.isna() 등을 이용하여 null값을 확인할 경우 null값이 없다고 나오게 됩니다. 이에 이 Data를 제대로 확인하기 위해서 "" 대신에 Null값을 넣으려 합니다
- 조건1: ""(또는 '')는 띄어쓰기 없이 쌍따옴표(혹은 따옴표)로만 구성됩니다.
- 조건2: ""(또는 '')를 null값(None)으로 바꾸세요.
- 조건3: Index 또는 순서(order)는 변경하지 마세요.
- 조건4: 'df_target' 변수에 결과 DataFrame을 할당하세요.
- 완료 후 결과 dataframe 변수(df_target)를 check_02_01 함수에 입력하여 채점하세요.
# 2-1
# 모든 값에 적용하기 위해 apply 사용
df_target = df_target.apply(lambda x: x.replace("", None))
check_02_01(df_target)
해당 문제는 매우 간단한 문제였습니다.
우선 모든 DataFrame에 적용을 시키기 위해 apply를 사용하고 lambda식을 이용하고 "" -> None으로 바꾸기 위해 문자열을 교체해주는 함수 replace(old, new)를 사용하여 모든 값들을 변경해주었습니다.
문제 2-2) 기초 전처리 02 (5점)
- 2-1의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- json Data를 Pandas DataFrame으로 만들면서 수치형 데이터들이 string으로 인식되었습니다. 이를 변경하려고 합니다.
- 조건1: 아래 type_int_col의 Column(열) Data를 정수(int)형 Data로 변경하세요.
- 조건2: 아래 type_float_col의 Column(열) Data를 실수(float)형 Data로 변경하세요.
- 조건3: 변경할 Data에 Null값이 있다면, 0으로 채우세요.
- 조건4: Index 또는 순서(order)는 변경하지 마세요.
- 조건5: 'df_target' 변수에 결과 DataFrame을 할당하세요.
- 완료 후 결과 dataframe 변수(df_target)를 check_02_02 함수에 입력하여 채점하세요.
type_int_col = ['어른관람료', '청소년관람료', '어린이관람료']
type_float_col = ['위도', '경도']
# 2-2
# int64로 하면 왜 정답이 아닐 까요??
df_target[type_int_col] = df_target[type_int_col].astype('int')
df_target[type_float_col] = df_target[type_float_col].astype('float')
# df_target.info() dptj 경도에서 Nan값 하나가 발견되어 0으로 변경
df_target[type_float_col] = df_target[type_float_col].fillna(0)
check_02_02(df_target)
이번 문제도 매우 간단하지만 하나의 문제점 때문에 오래 걸리기도 한 문제입니다.
이 문제는 간단하게 형변환하고 해당 컬럼 값에서 None값이 있다면 0으로 변환 해주는 문제라 astype()과 fillna() 두개의 함수를 이용하면 쉽게 풀 수 있는 문제였습니다.
하지만, astype('int64')를 사용하였더니 계속 답이 틀리는 상황이 생겼습니다. 그래서 뭐가 문제일 까 생각해본 후 chat GPT를 사용하여 물어보았습니다.
결과, int64는 pandas에서 사용하는 타입이고 채점하는 파일은 파이썬이라 두 개는 다른 타입이라고 인식하였던 것 같습니다. 그래서 해당 문제에 대해 정확히 파악하고 int64는 pandas에서만 사용하는 타입이라는 것을 알 수 있었습니다.
문제 2-3) 기초 전처리 03 (5점)
- 2-2의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- 분석과 상관 없는 Column(열)의 Data들을 삭제하여 Data의 가독성을 높이고자 합니다.
- 조건1: 아래 drop_col의 Column(열) Data를 삭제하세요.
- 조건2: Index 또는 순서(order)는 변경하지 마세요.
- 조건3: 'df_target' 변수에 결과 DataFrame을 할당하세요.
- 분석과 상관 없는 Column(열)의 Data들을 삭제하여 Data의 가독성을 높이고자 합니다.
- 완료 후 결과 dataframe 변수(df_target)를 check_02_03 함수에 입력하여 채점하세요.
drop_cols = ['소재지지번주소', '위도', '경도', '운영기관전화번호','운영기관명', '운영홈페이지', '편의시설정보', '휴관정보',
'관람료기타정보', '박물관미술관소개', '교통안내정보', '관리기관전화번호', '관리기관명', '제공기관코드', '제공기관명']
# 2-3
df_drop = df_target.drop(columns=drop_cols)
df_target = df_drop
check_02_03(df_target)
해당 문제 또한 많은 테스트에서 다뤄봤기에 drop()함수를 사용하여 가볍게 통과할 수 있었습니다.
문제 2-4) 기초 전처리 04 (5점)
- 2-3의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- 어른, 청소년, 어린이 관람료가 이상한 경우, 해당 row(행) data 자체가 이상하다고 판단하여 삭제하고자 합니다.
- 조건1: 관람료와 관련된 Column(열)은 위에서 정의한 type_int_col 입니다.
- 조건2: 관람료가 10원 단위로 나누어 떨어지지 않는 경우 이상치로 판단합니다. 해당 row(행)를 삭제하세요.
- 조건3: 관람료가 100000원(십만원) 이상인 경우 이상치로 판단합니다. 해당 row(행)를 삭제하세요.
- 조건4: Index 또는 순서(order)는 변경하지 마세요.
- 조건5: 'df_target' 변수에 결과 DataFrame을 할당하세요.
- 어른, 청소년, 어린이 관람료가 이상한 경우, 해당 row(행) data 자체가 이상하다고 판단하여 삭제하고자 합니다.
- 완료 후 결과 dataframe 변수(df_target)를 check_02_04 함수에 입력하여 채점하세요.
# 2-4
df_del = df_target.copy()
# 해당 인트형 행을 모두 검사 -> i = index, v=행의 값
for i, v in df_del[type_int_col].iterrows():
# flag를 통해 마지막에 True이면 해당 행 삭제
flag = False
# v의 값은 3개가 있음('어른관람료', '청소년관람료', '어린이관람료') 각각의 값을 확인 하기 위해 한번더 for문
for j in range(len(v)):
if v[j] % 10 != 0:
flag = True
elif v[j] >= 100000:
flag = True
if flag:
df_del.drop(index=i, axis=0, inplace=True)
df_target = df_del
check_02_04(df_target)
해당 문제는 조건에 주어진 내용을 통해 이상치를 찾아내어 해당 이상치가 있는 행을 삭제하는 작업을 요구하는 문제입니다.
그래서 저는 문제에서 관람료와 관련된 컬럼인 열들만 가져와서 iterrows()를 사용하여 모든 행들을 확인 할 것입니다.
이제 flag는 삭제 할 행이 있다면 True로 바꿔주어 삭제할 행, 삭제하지 않을 행인지 구별해주는 역할을 합니다.
두번째 for문은 v의 값에 '어른관람료', '청소년관람료', '어린이관람료'가 들어있습니다. 그래서 해당 값들도 모두 들어가서 조건에 맞는지 확인하기 위해서 사용하였습니다. -> 반복문을 돌면서 해당 관람료를 한 번씩 다 확인하게 될 것입니다.
그리고 마지막에 flag가 True라면 저희는 해당 인덱스인 i를 알고 있으므로 인덱스를 사용하여 삭제해줍니다.
시행착오
"저는 여기서도 좀 막혔었는데 원래 변수 flag 없이 이중 for문 내부에서 삭제를 하려 했었습니다. 그렇게 되면 문제점이 v[0]이 조건이 일치하여 삭제가 되었는데 for문에 의해서 v[1]값도 확인을 해야 하는데 이미 v[0]에서 행을 삭제하여 해당 v는 없어져 버려 코드가 고장나게 되는 것이였습니다. (지금 생각하면 확실히 말이 안됩니다.)"
3단계: DataFrame 전처리 02 - 심화
문제 3-1) 심화 전처리 01 (10점)
- 2단계의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- 휴관중이거나 중복된 박물관/미술관의 data를 삭제하고자 합니다.
- 아래의 조건 이외에도 중복되는 data들이 있으나, 해당 Test에서는 아래 조건에 따른 중복 data만 삭제하여 진행합니다
- 휴관중이거나 중복된 박물관/미술관의 data를 삭제하고자 합니다.
- 조건1: 시설명 Column(열) data에 '휴관'이라는 글자가 들어있으면 해당 row(행)은 삭제합니다.
- 조건2: 시설명 Column(열) data가 중복되는 경우 해당 row(행)의 '데이터기준일자'가 최신인 data를 남기고 최신이 아닌 row(행)은 삭제합니다.
- 조건3: 시설명 Column(열) data의 중복 여부는 시설명 Column(열) data의 띄어쓰기를 삭제한 값이 일치할 경우 중복된 박물관/미술관으로 판단합니다.
- 조건4: Index 또는 순서(order)는 변경하지 마세요. - 문제를 풀기위해 순서를 변경하였다면, 다시 Index 순서로 정렬하세요
- 조건5: 'df_target' 변수에 결과 DataFrame을 할당하세요.
# 3-1
# 해당 문제에서는 데이터기준일자의 형식(년-월-일) 특성상 타입을 변경하지 않아도 정상적으로 대소비교 가능합니다..
# 따라서 아래 주석과 같이 format을 변경할 필요 없습니다.
from datetime import date
# df_target.데이터기준일자 = df_target.데이터기준일자.apply(date.fromisoformat)
# 방법1
# 결과 dataframe에 index name이 지정되어있으나 방법2와 동일한 dataframe입니다.
df_target_temp = df_target.copy()
# 조건 1 -> ~ = not
df_target_temp = df_target_temp[~(df_target_temp['시설명'].str.contains('휴관'))]
# 시설명에 있는 공백 제거
df_target_temp['시설명'] = df_target_temp['시설명'].str.replace(" ", "")
# 데이터기준일자, index로 정렬 하되 데이터 기준일자를 내림차순으로 정렬 뒤 같은 게 있다면 index로 오름차순 정렬
df_target_temp = df_target_temp.sort_values(by=['데이터기준일자', 'index'], ascending=[False, True])
# 이제 중복 값 중 첫번째 값 빼고 모두 삭제
df_target_temp.drop_duplicates(subset=['시설명'], keep='first', inplace=True)
# 이제 처음 인덱스로 되돌리기 위해서 기존 index로 오름차순 정렬
df_target_temp = df_target_temp.sort_values(by='index')
check_03_01(df_target_temp)
display(df_target_temp.head())
df_target = df_target_temp.copy()
1. 조건 1에서 시설명에 휴관이 적혀있으면 해당 행을 삭제하는 문제이기에 우선 해당 컬럼명만 구분하면 되기에 문자열로 바꿔준 뒤 contains('휴관') 함수를 이용해 휴관이 포함된 데이터들을 찾아줍니다.
그 후 논리 연산자 Not(~)을 이용해 해당 행을 빼고 저장해주었습니다.
2. 조건 2는 해당 데이터에는 중복된 시설들이 많이 존재합니다. 하지만 최신 데이터만 필요할 뿐이니 나머지 데이터는 삭제하라는 문제입니다. 그래서 생각해낸 것은 우선 drop_duplicates()를 사용하는 것인데 이제 어떻게 하나만 남기고 삭제할 것인지 생각하던 중 drop_duplicates()의 기능 중 중복되는 값들 중 첫번째만 남기고 삭제하는 기능을 찾았습니다.
그래서 sort_values() 함수를 통해 ['데이터기준일자'] 해당 열을 기준으로 내림차순 정렬하고 같은 것이 있다면 'index'를 이용해 또 정렬해주고 drop_duplicates()의 keep='first'를 사용하여 최신 데이터인 값만 남기고 중복값은 삭제 할 수 있었습니다.
3. 조건 3의 내용을 맞추기 위해 조건 1에서 한 것처럼 replace()를 사용하여 공백을 제거해주었습니다.
4. 이제 마지막으로 처음 인덱스로 돌려놓기 위해 인덱스를 기준으로 정렬을 해주었습니다.
시행착오
역시 drop_duplicated 활용 하는 것이였습니다. 구글링을 하다가 찾은 내용이라 빠르게 이에 해당하는 문제는 쉽게 풀 수 있었으나, 문제에서 df_target에 저장하라는 조건을 제대로 하지 않고 해당 문제로 오니 위에서 전처리 한 데이터를 쓰지 않고 처음에 있던 df_target 데이터를 사용하여 문제들은 맞췄으나, 기본적인 것에서 부터 틀리게 되어 시간을 많이 사용했었습니다.
문제 3-2) 심화 전처리 02 (10점)
- 3-1의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- 평일과 공휴일에 박물관/미술관이 하루 중 몇시간이나 열려있는지를 알려주는 '관람가능시간'을 구하려고 합니다.
- 조건1: 평일의 관람가능시간은 '평일관람시작시각'부터 '평일관람종료시각'까지 입니다. '평일관람가능시간' Column(열)을 만들어 평일의 관람가능시간을 입력하세요.
- 조건2: 공휴일의 관람가능시간은 '공휴일관람시작시각'부터 '공휴일관람종료시각'까지 입니다. '공휴일관람가능시간' Column(열)을 만들어 공휴일의 관람가능시간을 입력하세요.
- 조건3: '평일관람가능시간'과 '공휴일관람가능시간'은 시간(hour) 단위 실수(float)로 표기합니다.
- 관람가능시간이 8시간 30분인경우 8.5로 표기합니다.
- 관람가능시간이 23시간을 초과하는 경우 24시간으로 표기합니다.
- 평일 또는 공휴일의 관람시작시각과 관람종료시각이 모두 00:00(또는 0:00)인 경우 휴일로 판단하며, 관람가능시간은 0으로 입력합니다.
- 관람가능시간이 6시간 40분과 같이 무환소수(6.6666666666666......6666666667)로 표기될 경우 소숫점 셋째 자리에서 반올림하여 소숫점 둘째 자리까지 표기합니다.
- 평일과 공휴일에 박물관/미술관이 하루 중 몇시간이나 열려있는지를 알려주는 '관람가능시간'을 구하려고 합니다.
# 3-2
# 아래 방법들은 새벽에 관람을 종료하는 박물관/미술관이 없음을 확인했기 때문에 가능한 코드입니다.
# 만약 10:00(관람시작시각) ~ 02:00(관람종료시각)이라면, 하기 코드로는 음수(-8)가 나오기때문에 24를 더한 16시간이라고 표기되어야 합니다.
# 방법1
df_target_temp = df_target_temp.copy()
# 각 컬럼을 timedelta로 변형 후 차이를 계산한 후 total_second()로 바꾼 후 나누기 3600을 하였음.
df_target_temp['평일관람가능시간'] = round((pd.to_timedelta(df_target_temp['평일관람종료시각'] + ':00') - pd.to_timedelta(df_target_temp['평일관람시작시각'] + ':00')).dt.total_seconds() / 3600, 2)
for idx, values in df_target_temp.iterrows():
if values['평일관람가능시간'] < 0:
df_target_temp.loc[idx, '평일관람가능시간'] = values['평일관람가능시간'] + 24
elif values['평일관람가능시간'] > 23.00:
df_target_temp.loc[idx, '평일관람가능시간'] = 24.00
df_target_temp['공휴일관람가능시간'] = round((pd.to_timedelta(df_target_temp['공휴일관람종료시각'] + ':00') - pd.to_timedelta(df_target_temp['공휴일관람시작시각'] + ':00')).dt.total_seconds() / 3600, 2)
for idx, values in df_target_temp.iterrows():
if values['공휴일관람가능시간'] < 0:
df_target_temp.loc[idx, '공휴일관람가능시간'] = values['공휴일관람가능시간'] + 24
elif values['공휴일관람가능시간'] > 23.00:
df_target_temp.loc[idx, '공휴일관람가능시간'] = 24.00
check_03_02(df_target_temp)
display(df_target_temp.head())
df_target = df_target_temp.copy()
1. 시간을 계산하기 위해 '평일관람가능시간', '평일관람시작시각'을 timedelta 타입으로 바꿔준 뒤 두 개의 값을 빼주었습니다. 이제 빼준 값을 datetime(dt)로 바꿔주고 초로 바꿔(total_second()) 3600으로 나눠주었습니다. 그리고 소수점 두번째 자리에서 올려주기 위해 round함수를 사용하였습니다.
2. 이제 계산 된 값들 중에서 0보다 이하인 값이 있으면 24를 더해 주었고 23보다 크다면 24로 설정해주었습니다.
3. 마지막으로 공휴일 또한 위의 방식으로 똑같이 해주었습니다.
시행착오
여기서는 시간 계산이 문제였습니다. 그래서 datetime을 사용해서 계산 시도도 해보고 하였으나, datetime타입으로는 계산할 수 없고 timedelta 타입으로는 계산이 되는 것을 확인하여 해당 타입으로 계산해보는 시도도 해보는 작업을 하여 연습을 하고 문제에 직접 적용하여 해당 문제를 해결 하였습니다.
문제 3-3) 심화 전처리 03 (10점)
- 3-2의 DataFrame(df_target)을 아래의 조건에 맞게 변경하세요.
- '소재지도로명주소' Column(열)의 Data를 가공하여 광역자치단체-기초자치단체(행정시)-상세 주소로 구분하려고 합니다.
- 조건1: '소재재도로명주소' Column(열) data의 첫번째 단어는 언제나 광역자치단체명을 의미합니다. '광역' Column(열)을 만들어 해당 row(행) data의 광역자치단체명을 입력하세요.
- '세종특별시'는 현재 '세종특별자차시'로 명칭이 변경되었습니다. 이를 반영해주세요.
- '소재지도로명주소' Column(열)의 Data를 가공하여 광역자치단체-기초자치단체(행정시)-상세 주소로 구분하려고 합니다.
- 조건2: '소재재도로명주소' Column(열) data의 두번째 단어는 대부분 기초자치단체명을 의미합니다. '기초' Column(열)을 만들어 해당 row(행) data의 기초자치단체명을 입력하세요. - '제주특별자치도'의 경우 기초자치단체가 없으나, 행정시('제주시', '서귀포시')가 '소재재도로명주소' Column(열) data의 두번째 단어에 위치합니다. 행정시를 '기초' Column(열)에 입력하세요. - '세종특별자치시'의 경우 기초자치단체가 없습니다. '세종특별자치시'의 경우 '기초' Column(열)에는 Null값(None)을 입력해주세요.
- 조건3: '소재재도로명주소' Column(열) data에서 광역/기초자치단체(행정시포함)에 포함되지 않은 데이터는 '상세' Column(열)을 만들어 입력하세요.
- 조건4: '소재지도로명주소', '광역', '기초', '상세 Column(행)의 data는 해당 data의 앞-뒤로 띄어쓰기 등 공백이 없어야 합니다.
- 조건5: Index 또는 순서(order)는 변경하지 마세요.
- 조건6: 'df_target' 변수에 결과 DataFrame을 할당하세요.
# 3-3
# 방법1
df_target_temp = df_target.copy()
a_list = [] # 광역
b_list = [] # 기초
c_list = [] # 상세
df_target_temp['소재지도로명주소'] = df_target_temp['소재지도로명주소'].str.replace('세종특별시', '세종특별자치시')
for i, v in enumerate(df_target_temp['소재지도로명주소']):
split_list = v.split()
if '세종특별자치시' in v:
a_list.append(split_list[0])
b_list.append(None)
c_list.append(" ".join(split_list[1:]))
else :
a_list.append(split_list[0])
b_list.append(split_list[1])
c_list.append(" ".join(split_list[2:]))
df_target_temp['광역'] = a_list
df_target_temp['기초'] = b_list
df_target_temp['상세'] = c_list
df_target_temp['소재지도로명주소'] = df_target_temp['소재지도로명주소'].str.strip()
check_03_03(df_target_temp)
display(df_target_temp.head())
df_target = df_target_temp.copy()
1. 저는 이 문제를 정말 단순하게 생각하였습니다. 3개의 리스트를 생성하고 split을 통해 공백 기준으로 쪼개준 뒤 join과 슬라이싱을 통해 끝의 주소들을 합쳐주는 것을 떠올렸습니다.
2. a,b,c 리스트를 만들고 세종특별시는 replace를 통해 세종특별자치시로 바꿔주었고 for문을 통해 도로명 주소의 값들을 하나하나 split을 통해 잘라 준 뒤 a에는 split된 0번 인덱스를 b에는 1번 인덱스 그리고 c에는 나머지 값들을 join()함수를 통해 함쳐준 뒤 [2:] 슬라이싱을 하여 넣어주었습니다.
만약 세종특별자치시가 온다면 b인덱스에 None을 넣어주고 인덱스를 앞당겨 합쳐주었습니다.
3. 앞 뒤 공백을 없애기 위해 strip()함수를 사용하였습니다.
시행착오
해당 문제는 그래도 꽤나 빨리 풀 수 있었습니다. 여기서 제가 문제하나를 대충읽고 앞 뒤 공백을 안해주는 작업을 해버린 바람에 또 시간을 지체하였습니다. 그래도 빨리 strip()함수를 사용하여 문제를 해결하였습니다.
4단계: 원하는 정보 얻기
문제 4-1) 원하는 정보 얻기 01 (10점)
- 3단계의 DataFrame(df_target)을 이용하여 아래의 조건에 맞는 정보를 구하세요.
- 광역자치단체별 박물관/미술관의 총 수를 확인하고자 합니다.
- 조건1: df_target의 '광역' Column(열)에 있는 광역자치단체 data를 이용하여 광역자치단체별 박물관/미술관의 총 수를 나타내주세요.
- 조건2: 결과 DataFrame의 Index는 광역자치단체입니다. 광역자치단체의 우선순위는 아래 province_dict의 value(값)으로 제공합니다. Index의 순서를 광역자치단체의 우선순위에 따라 나열해주세요.
- 출처: 행정안전부
- 광역자치단체별 박물관/미술관의 총 수를 확인하고자 합니다.
- 조건3: 결과 DataFrame의 박물관/미술관의 총 수를 나타내는 Column(열)의 이름은 '박물관미술관수' 입니다.
province_dict = {
'서울특별시': 0,
'부산광역시': 1,
'대구광역시': 2,
'인천광역시': 3,
'광주광역시': 4,
'대전광역시': 5,
'울산광역시': 6,
'세종특별자치시': 7,
'경기도': 8,
'강원도': 9,
'충청북도': 10,
'충청남도': 11,
'전라북도': 12,
'전라남도': 13,
'경상북도': 14,
'경상남도': 15,
'제주특별자치도': 16
}
# 4-1
# 방법1
df_result = df_target.pivot_table(index='광역',values='시설명', aggfunc='count').reset_index()
df_result.rename(columns={'시설명':'박물관미술관수'}, inplace=True)
df_result['df_rank'] = df_result['광역'].map(province_dict)
df_result = df_result.sort_values(by='df_rank').set_index('광역').drop(columns='df_rank',axis=1)
check_04_01(df_result)
display(df_result)
# 방법2
# check_04_01(df_result)
# display(df_result)
해당 문제는 데이터 찾는 문제라 쉽게 풀 수 있었습니다. 우선 앞에서 했던 EDA 테스트의 내용을 기억하고 있어서 매우 쉽게 풀 수 있었습니다.
우선 딕셔너리를 준 것으로 보아 "아! 매핑 하면 쉽게 풀 수 있게구나"를 생각했습니다.
1. 먼저 pivot_table을 통해 '광역'을 인덱스에 넣고 value값으로 '시설명' 그리고 집계함수로 count를 사용하였습니다.
2. 시설명을 박물관 미술관 수로 이름을 변경하기위해 rename을 사용하였습니다.
3. 광역을 기준으로 map()을 사용하여 매핑 시킨 뒤 'df_rank'를 기준으로 정렬해주고 해당 컬럼은 이제 사용할 필요가 없어 삭제 해준 결과 원하는 결과를 얻어올 수 있었습니다.
문제 4-2) 원하는 정보 얻기 02 (10점)
- 3단계의 DataFrame(df_target)을 이용하여 아래의 조건에 맞는 정보를 구하세요.
- 광역자치단체-기초자치단체(행정시)의 박물관/미술관의 총 수가 8개인 광역-기초자치단체(행정시)를 확인하고자 합니다.
- 조건1: df_target의 '광역'과 '기초' Column(열)에 있는 광역자치단체/기초자치단체(행정시) data를 이용하여 광역자치단체-기초자치단체(행정시)별 박물관/미술관의 총 수가 8개인 곳을 찾아주세요.
- 조건2: 결과 DataFrame의 '광역' Column(열)에 광역자치단체를, '기초' Column(열)에 기초자치단체(행정시)를 입력해주세요.
- 조건3: '광역' Column(열)은 4-1문제와 같이 광역자치단체의 우선순위에 따라 나열해주세요.
- 4-1의 province_dict 참고
- 광역자치단체-기초자치단체(행정시)의 박물관/미술관의 총 수가 8개인 광역-기초자치단체(행정시)를 확인하고자 합니다.
- 조건4: 같은 광역자치단체가 있다면, '기초' Column(열)의 data는 가나다 순의 역순으로 나열해주세요.
- 조건5: 결과 DataFrame의 박물관/미술관의 총 수를 나타내는 Column(열)의 이름은 '박물관미술관수' 입니다.
- 조건6: Index는 숫자(정수) 오름차순으로 설정해주세요.
- 조건7: 'df_result' 변수에 결과 DataFrame을 할당하세요.
# 4-2
df_result = df_target.pivot_table(index=['광역','기초'],values='시설명', aggfunc='count', dropna=False).reset_index()
df_result.rename(columns={'시설명':'박물관미술관수'}, inplace=True)
df_result['df_rank'] = df_result['광역'].map(province_dict)
df_result = df_result.sort_values(by=['df_rank','기초'], ascending=[True, False]).drop(columns='df_rank',axis=1)
df_result = df_result[df_result['박물관미술관수'] == 8].reset_index(drop=True)
display(df_result)
check_04_02(df_result)
1. 해당 문제는 4-1번 문제와 매우 유사하여 쉽게 풀 수 있었습니다. 그래서 거의 내용이 비슷합니다.
2. 여기서 조건 6은 해당 인덱스를 다시 설정하여 1부터 다시 나오도록 만드는 조건인 것 같습니다.
시행착오
해당 문제를 푸는데 분명 틀린게 없었는데 계속 틀렸다 나와서 문제가 잘못됬나 싶어, 문제 채점하는 python파일을 직접 확인한 결과 기초가 Null값인 것을 확인하는 것을 보고 알았습니다.
제가 세종특별자치시를 고려하지 않고 계산하는 바람에 틀렸다는 것을 말이죠... 그래서 pivot_table에 대해 더 알아보다 dropna라는 argument를 확인한 결과 Nan값이 있어도 없애지 않고 출력해주는 역할이여서 False로 설정해준 결과 해결을 바로 할 수 있었습니다.
문제 4-3) 원하는 정보 얻기 03 (10점)
- 3단계의 DataFrame(df_target)을 이용하여 아래의 조건에 맞는 정보를 구하세요.
- 광역자치단체-박물관미술관구분(사립, 국립, 공립, 대학)의 평균 관람료 차이를 알아보고자 합니다.
- 조건1: df_target의 '광역'과 '박물관미술관구분' Column(열)에 있는 광역자치단체/박물관미술관구분 data를 이용하여 광역자치단체-박물관미술관구분별 평균 어른관람료-평균 어린이관람료 간 차이가 가장 크고 작은 곳을 찾아주세요.
- 단, 어른관람료 또는 어린이관람료가 둘 중 하나라도 0원(무료)인 박물관/미술관의 경우 평균 계산에서 제외해주세요.
- 광역자치단체-박물관미술관구분(사립, 국립, 공립, 대학)의 평균 관람료 차이를 알아보고자 합니다.
- 조건3: 결과 DataFrame의 '광역' Index에 광역자치단체를, '박물관미술관구분' Index에 박물관미술관구분를 입력해주세요.
- 조건4: '광역' Index은 4-1문제와 같이 광역자치단체의 우선순위에 따라 나열해주세요. - 4-1의 province_dict 참고
- 조건5: 결과 DataFrame의 '어른관람료' Column(열)은 광역자치단체-박물관미술관구분별 평균 어른 관람료를, '어린이관람료' Column(열)은 광역자치단체-박물관미술관구분별 평균 어린이 관람료를, '관람료차이' Column(열)은 광역자치단체-박물관미술관구분별 평균 어른 관람료 - 평균 어린이 관람료(차액)을 입력해주세요. - 어른/어린이 관람료 및 관람료차이는 평균값에서 소숫점 첫째 자리에서 반올림한 정수 값을 입력해주세요. - 예시: 2978.5원 -> 2980.0원(소숫점 첫째 자리에서 반올림) -> 2980원(정수 값)
# 4-3
import numpy as np
df_result = df_target[(df_target['어른관람료'] != 0) & (df_target['어린이관람료'] != 0)]
df_result = df_result.pivot_table(index=['광역', '박물관미술관구분'], values=['어른관람료', '어린이관람료'], aggfunc='mean').reset_index()
df_result['어른관람료'] = np.ceil(df_result['어른관람료'] / 10) * 10
df_result['어린이관람료'] = np.ceil(df_result['어린이관람료'] / 10) * 10
df_result['관람료차이'] = df_result['어른관람료'] - df_result['어린이관람료']
# 여기서 데이터 타입이 바뀌어버림.. float -> object
diff_max = df_result.loc[df_result['관람료차이'].idxmax()].to_frame().T
diff_min = df_result.loc[df_result['관람료차이'].idxmin()].to_frame().T
df_result = pd.concat([diff_min, diff_max])
# 여기 부분에서 바꿔줘야만 int로 설정됨.
df_result['어른관람료'] = df_result['어른관람료'].astype(int)
df_result['어린이관람료'] = df_result['어린이관람료'].astype(int)
df_result['관람료차이'] = df_result['관람료차이'].astype(int)
df_result['df_rank'] = df_result['광역'].map(province_dict)
df_result = df_result.set_index(['광역', '박물관미술관구분']).sort_values(by='df_rank').drop(columns='df_rank')
display(df_result.head())
check_04_03(df_result)
1. 논리연산자 OR(|)를 사용하여 어른, 어린이 관람료 중 하나라도 0인 값은 포함시키지 않게 하였습니다.
2. 소수 첫째 자리에서 반올림을 해주면서 10으로 나누어 떨어질 수 있도록 만들고 소수점이 없도록 만들어 줘야하기 때문에 numpy함수의 ceil()함수를 사용하여 반올림 해주었으며, 10으로 나누어 떨어질 수 있도록 반올림 된 값들을 10으로 나눠준 뒤 다시 10으로 곱해주는 작업을 하였습니다. (이 때 타입은 float형)
3. 이제 관람료 차이가 가장 큰 인덱스(idxmax())와 가장 작은 인덱스(idxmin())를 찾은뒤 해당 인덱스의 값을 가져와서 DataFrame으로 만들어 준 뒤 concat으로 합치는 작업을 하였습니다.
4. 어른 관람료와 어린이 관람료, 관람료 차이는 정수형이여야 하기 때문에 타입 변환을 해주었습니다.
시행착오
제가 2번 작업을 시행할 때 먼저 int 타입으로 변형을 해주고 작업을 계속 이어나갔지만 계속 정답이 아니라고 나왔습니다. 그래서 무엇이 문제인가 하다가 모든 구간을 dtypes()을 적용하여 어느 순간에 타입이 변경되는지 확인 한 결과 concat을 할 때 관람료 컬럼값들이 전부 objects로 타입이 변경되는 것이 확인되어 위에서 타입 변환하지 말고 차라리 concat으로 묶은 다음에 정수형으로 바꿔주는 해결책을 사용하였더니 바로 정답이 되었습니다.
문제 4-4) 원하는 정보 얻기 04 (10점)
- 3단계의 DataFrame(df_target)을 이용하여 아래의 조건에 맞는 정보를 구하세요.
- 가족(어른2, 청소년1, 어린이1)이 공휴일에 제주특별자치도 제주시에 있는 미술관을 관람하려 합니다. 총 관람료가 2만원 이하, 공휴일 4시간 이상 관람 가능한 미술관 list를 보여주세요.
- 조건1: 가족(어른 2명, 청소년 1명, 어린이 1명)의 총 관람료가 2만원 이하여야 합니다.
- 조건2: 제주특별자치도의 제주시에 있는 미술관을 가려고 합니다.
- 미술관: 이 Test에서는 df_target의 시설명 Column(열)에 있는 data 중 <'미술관' 또는 '갤러리' 또는 '아트'> 라는 글자들이 포함되어 있는 곳을 '미술관'이라고 정의합니다.
- 가족(어른2, 청소년1, 어린이1)이 공휴일에 제주특별자치도 제주시에 있는 미술관을 관람하려 합니다. 총 관람료가 2만원 이하, 공휴일 4시간 이상 관람 가능한 미술관 list를 보여주세요.
- 조건3: 공휴일에 가고자 합니다. 공휴일에 4시간 이상 관람 가능한 미술관이어야 합니다.
- 조건4: 미술관 List의 Frame은 df_target과 동일합니다.
# 4-4
budget = 20000
# 가족(어른2, 청소년1, 어린이1)이 공휴일에 제주특별자치도 서귀포시에 있는 미술관을 관람하려 합니다. 총 관람료가 2만원 이하, 공휴일 4시간 이상 관람 가능한 미술관 list를 보여주세요
# 미술관인 곳 설정
df_target = df_target[df_target['시설명'].str.contains('미술|갤러리|아트')==True]
# 제주특별자치도 제주시 인 곳.
df_target = df_target[(df_target['광역'] == '제주특별자치도') & (df_target['기초'] == '제주시')]
df_result = df_target[((df_target['어른관람료'] * 2 + df_target['청소년관람료'] * 1 + df_target['어린이관람료'] * 1) <= budget) &
(df_target['공휴일관람가능시간'] >= 4.0)]
check_04_04(df_result)
해당 문제는 매우 쉬웠습니다.
1. '미술', '갤러리', '아트'가 포함된 곳이 미술관으로 모두 해당되어 '시설명' 컬럼의 값에서 contains()함수를 사용하여 해당 단어들이 포함된 값들만 나오도록 저장해주었습니다.('미술|갤러리|아트' 이렇게 설정하면 해당 각각의 단어들이 있는지 확인 가능합니다.)
2. 논리연산자를 사용하여 제주특별자치도이면서 제주시인 곳을 저장해주었습니다.
3. 가격 비교를 하기위해 계산을 한 뒤 논리연산자를 사용하여 4.0보다 큰 곳만 찾도록 설정한 결과 정답을 찾을 수 있었습니다.
이상으로 EDA 분석 테스트를 마치겠습니다.
이 글은 제로베이스 데이터 분석 취업 스쿨의 강의 자료 일부를 발췌하여 작성되었습니다.
'Project > EDA 연습' 카테고리의 다른 글
[Zero-base] EDA 4회차 테스트 (1) | 2024.09.27 |
---|---|
[Zero-base] EDA 3회차 테스트 (0) | 2024.09.24 |