import json
with open('./datas/전국박물관미술관정보표준데이터.json', 'r', encoding='utf-8') as f:
json_data = json.load(f)
1단계: Json Data로 DataFrame 만들기
- json_data
- json_data['fields']: List. 각 variable은 하나의 Column(열)을 의미하며, {id: Column(열)명}의 형식으로 구성 되어있습니다.
- json_data['records']: List. 각 variable은 하나의 Row(행)을 의미하며, {Column(열)명: data}의 형식으로 구성 되어있습니다.
columns = []
for i in json_data['records'][0]:
columns.append(i)
df = pd.DataFrame()
for data in json_data['records']:
df = pd.concat([df, pd.DataFrame([data])], ignore_index=True)
2단계: DataFrame 전처리 01 - 기초
- 해당 json_data의 null값은 ""로 구성되어
df_target.info() 또는 df_target.isna() 등을 이용하여 null값을 확인할 경우 null값이 없다고 나오게 됩니다. 이에 이 Data를 제대로 확인하기 위해서 "" 대신에 Null값을 넣으려 합니다.
- 조건1: ""(또는 '')는 띄어쓰기 없이 쌍따옴표(혹은 따옴표)로만 구성됩니다.
- 조건2: ""(또는 '')를 null값(None)으로 바꾸세요.
for i in range(0, len(df)):
for j in range(0, len(columns)):
if df.iloc[i, j] == '':
df.iloc[i, j] = None
문제 2-2) 기초 전처리 02
- 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으로 채우세요.
type_int_col = ['어른관람료', '청소년관람료', '어린이관람료']
type_float_col = ['위도', '경도']
df_target[type_int_col] = df_target[type_int_col].astype(int)
df_target[type_float_col] = df_target[type_float_col].astype(float)
def fillNa(df, c):
df[c].fillna(0, inplace=True)
for column in type_int_col:
fillNa(df_target, column)
for column in type_float_col:
fillNa(df_target, column)
문제 2-2) 기초 전처리 03
drop_cols = ['소재지지번주소', '위도', '경도', '운영기관전화번호','운영기관명', '운영홈페이지', '편의시설정보', '휴관정보',
'관람료기타정보', '박물관미술관소개', '교통안내정보', '관리기관전화번호', '관리기관명', '제공기관코드', '제공기관명']
df_target = df_target.drop(columns=drop_cols)
문제 2-2) 기초 전처리 04
- 어른, 청소년, 어린이 관람료가 이상한 경우, 해당 row(행) data 자체가 이상하다고 판단하여 삭제하고자 합니다.
- 조건1: 관람료와 관련된 Column(열)은 위에서 정의한
type_int_col 입니다.
- 조건2: 관람료가 10원 단위로 나누어 떨어지지 않는 경우 이상치로 판단합니다. 해당 row(행)를 삭제하세요.
- 조건3: 관람료가 100000원(십만원) 이상인 경우 이상치로 판단합니다. 해당 row(행)를 삭제하세요.
index_wrong = []
import copy
df_target2 = copy.deepcopy(df_target)
for idx, row in df_target2.iterrows():
for column in type_int_col:
if row[column] % 10 != 0:
index_wrong.append(idx)
if row[column] >= 100000:
index_wrong.append(idx)
index_wrong = list(set(index_wrong))
df_target2 = df_target2.drop(index_wrong)
3단계: DataFrame 전처리 02 - 심화
문제 3-1) 심화 전처리 01
- 휴관중이거나 중복된 박물관/미술관의 data를 삭제하고자 합니다.
- 조건1: 시설명 Column(열) data에 '휴관'이라는 글자가 들어있으면 해당 row(행)은 삭제합니다.
- 조건2: 시설명 Column(열) data가 중복되는 경우 해당 row(행)의 '데이터기준일자'가 최신인 data를 남기고 최신이 아닌 row(행)은 삭제합니다.
- 만약, 시설명 Column(행)의 data가 중복되면서 가장 최신인 data가 두 개 이상인 경우, Index가 가장 낮은(예시: 495, 674 중 495) Index를 남기고, 높은 Index(예시: 495, 674 중 674)의 row(행) data를 삭제하세요.(낮은 인덱스가 더 최근 데이터라고 가정)
- 조건3: 시설명 Column(열) data의 중복 여부는 시설명 Column(열) data의 띄어쓰기를 삭제한 값이 일치할 경우 중복된 박물관/미술관으로 판단합니다.
for idx, row in df_target3.iterrows():
column_name = '시설명'
if '휴관' in row[column_name]:
df_target3 = df_target3.drop(idx)
def compareDate(a, b):
date1 = [int(part) for part in a.split('-')]
date2 = [int(part) for part in b.split('-')]
if date1 < date2:
return 'second'
elif date1 > date2:
return 'first'
else:
return 'same'
def duplicateCheck(df):
duplicate_check = {}
duplicate_names = set()
remove_rows = []
for idx, row in df.iterrows():
name = '시설명'
date = '데이터기준일자'
mus = row[name].replace(' ', '')
if mus not in duplicate_names:
duplicate_names.add(mus)
duplicate_check[mus] = [idx, row[date]]
else:
date_ori = duplicate_check[mus][1]
date_new = row[date]
comparson_result = compareDate(date_ori, date_new)
if comparson_result == 'first':
remove_rows.append(idx)
elif comparson_result == 'second':
remove_rows.append(duplicate_check[mus][0])
elif comparson_result == 'same':
remove_rows.append(duplicate_check[mus][0])
return df.drop(remove_rows)
while len(df_target4) != len(duplicateCheck(df_target4)):
df_target4 = duplicateCheck(df_target4)
문제 3-2) 심화 전처리 02
- 평일과 공휴일에 박물관/미술관이 하루 중 몇시간이나 열려있는지를 알려주는 '관람가능시간'을 구하려고 합니다.
- 조건1: 평일의 관람가능시간은 '평일관람시작시각'부터 '평일관람종료시각'까지 입니다. '평일관람가능시간' Column(열)을 만들어 평일의 관람가능시간을 입력하세요.
- 조건2: 공휴일의 관람가능시간은 '공휴일관람시작시각'부터 '공휴일관람종료시각'까지 입니다. '공휴일관람가능시간' Column(열)을 만들어 공휴일의 관람가능시간을 입력하세요.
- 조건3: '평일관람가능시간'과 '공휴일관람가능시간'은 시간(hour) 단위 실수(float)로 표기합니다.
- 관람가능시간이 8시간 30분인경우 8.5로 표기합니다.
- 관람가능시간이 23시간을 초과하는 경우 24시간으로 표기합니다.
- 평일 또는 공휴일의 관람시작시각과 관람종료시각이 모두 00:00(또는 0:00)인 경우 휴일로 판단하며, 관람가능시간은 0으로 입력합니다.
- 관람가능시간이 6시간 40분과 같이 무환소수(6.6666666666666......6666666667)로 표기될 경우 소숫점 셋째 자리에서 반올림하여 소숫점 둘째 자리까지 표기합니다.
df_target5['평일관람가능시간'] = None
df_target5['공휴일관람가능시간'] = None
from datetime import datetime
def calTime(start, end):
time_format = '%H:%M'
start = datetime.strptime(start, time_format)
end = datetime.strptime(end, time_format)
dif = end - start
seconds = dif.total_seconds()
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
if hours + (minutes / 60) > 23:
result = 24
else:
result = hours + (minutes / 60)
return round(result, 2)
for idx, row in df_target5.iterrows():
df_target5.at[idx, '평일관람가능시간'] = calTime(row['평일관람시작시각'], row['평일관람종료시각'])
df_target5.at[idx, '공휴일관람가능시간'] = calTime(row['공휴일관람시작시각'], row['공휴일관람종료시각'])
문제 3-3) 심화 전처리 03
- '소재지도로명주소' Column(열)의 Data를 가공하여 광역자치단체-기초자치단체(행정시)-상세 주소로 구분하려고 합니다.
- 조건1: '소재재도로명주소' Column(열) data의 첫번째 단어는 언제나 광역자치단체명을 의미합니다. '광역' Column(열)을 만들어 해당 row(행) data의 광역자치단체명을 입력하세요.
- '세종특별시'는 현재 '세종특별자차시'로 명칭이 변경되었습니다. 이를 반영해주세요.
- 조건2: '소재재도로명주소' Column(열) data의 두번째 단어는 대부분 기초자치단체명을 의미합니다. '기초' Column(열)을 만들어 해당 row(행) data의 기초자치단체명을 입력하세요.
- '제주특별자치도'의 경우 기초자치단체가 없으나, 행정시('제주시', '서귀포시')가 '소재재도로명주소' Column(열) data의 두번째 단어에 위치합니다. 행정시를 '기초' Column(열)에 입력하세요.
- '세종특별자치시'의 경우 기초자치단체가 없습니다. '세종특별자치시'의 경우 '기초' Column(열)에는 Null값(None)을 입력해주세요.
- 조건3: '소재재도로명주소' Column(열) data에서 광역/기초자치단체(행정시포함)에 포함되지 않은 데이터는 '상세' Column(열)을 만들어 입력하세요.
- 조건4: '소재지도로명주소', '광역', '기초', '상세 Column(행)의 data는 해당 data의 앞-뒤로 띄어쓰기 등 공백이 없어야 합니다.
df_target6['광역'] = None
df_target6['기초'] = None
df_target6['상세'] = None
def add(address, check):
addlist = address.split()
if check == '광역':
add = addlist[0]
if add in ['세종특별시', '세종특별자치시']:
add = '세종특별자치시'
elif check == '기초':
add = addlist[1]
if addlist[0] in ['세종특별시', '세종특별자치시']:
add = None
elif check == '상세':
add = ' '.join(addlist[2:])
if addlist[0] in ['세종특별시', '세종특별자치시']:
add = ' '.join(addlist[1:])
elif check == '소재지':
add = address.replace('세종특별시', '세종특별자치시').strip()
return add
for idx, row in df_target6.iterrows():
df_target6.at[idx, '소재지도로명주소'] = add(row['소재지도로명주소'], '소재지')
df_target6.at[idx, '광역'] = add(row['소재지도로명주소'], '광역')
df_target6.at[idx, '기초'] = add(row['소재지도로명주소'], '기초')
df_target6.at[idx, '상세'] = add(row['소재지도로명주소'], '상세')
4단계: 원하는 정보 얻기
문제 4-1) 원하는 정보 얻기 01
- 광역자치단체별 박물관/미술관의 총 수를 확인하고자 합니다.
- 조건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
}
df_result = pd.DataFrame(index=list(province_dict.keys()))
df_result = df_result.rename_axis('광역')
df_result['박물관미술관수'] = 0
for idx, row in df_target6.iterrows():
for ad in province_dict.keys():
if row['광역'] == ad:
df_result['박물관미술관수'][ad] += 1
문제 4-2) 원하는 정보 얻기 02
- 광역자치단체-기초자치단체(행정시)의 박물관/미술관의 총 수가 8개인 광역-기초자치단체(행정시)를 확인하고자 합니다.
- 조건1: df_target의 '광역'과 '기초' Column(열)에 있는 광역자치단체/기초자치단체(행정시) data를 이용하여 광역자치단체-기초자치단체(행정시)별 박물관/미술관의 총 수가 8개인 곳을 찾아주세요.
- 조건2: 결과 DataFrame의 '광역' Column(열)에 광역자치단체를, '기초' Column(열)에 기초자치단체(행정시)를 입력해주세요.
- 조건3: '광역' Column(열)은 4-1문제와 같이 광역자치단체의 우선순위에 따라 나열해주세요.
- 조건4: 같은 광역자치단체가 있다면, '기초' Column(열)의 data는 가나다 순의 역순으로 나열해주세요.
- 조건5: 결과 DataFrame의 박물관/미술관의 총 수를 나타내는 Column(열)의 이름은 '박물관미술관수' 입니다.
df_result2 = pd.DataFrame()
df_result2['광역'] = df_target6['광역']
df_result2['기초'] = df_target6['기초']
pivot_result = df_result2.fillna({'기초': 0}).pivot_table(index=['광역', '기초'], values='박물관미술관수', aggfunc='sum')
df_result3 = pivot_result[pivot_result['박물관미술관수'] == 8].reset_index()
sorted_province = sorted(province_dict, key=province_dict.get)
df_result3['광역'] = pd.Categorical(df_result3['광역'], categories=sorted_province, ordered=True)
df_result3 = df_result3.sort_values(['광역', '기초'], ascending=[True, False])
df_result4 = df_result3.reset_index(drop=True)
for idx, row in df_result4.iterrows():
if row['광역'] == '세종특별자치시':
df_result4.at[idx, '기초'] = None
문제 4-3) 원하는 정보 얻기 03
- 광역자치단체-박물관미술관구분(사립, 국립, 공립, 대학)의 평균 관람료 차이를 알아보고자 합니다.
- 조건1: df_target의 '광역'과 '박물관미술관구분' Column(열)에 있는 광역자치단체/박물관미술관구분 data를 이용하여 광역자치단체-박물관미술관구분별 평균 어른관람료-평균 어린이관람료 간 차이가 가장 크고 작은 곳을 찾아주세요.
- 단, 어른관람료 또는 어린이관람료가 둘 중 하나라도 0원(무료)인 박물관/미술관의 경우 평균 계산에서 제외해주세요.
- 조건3: 결과 DataFrame의 '광역' Index에 광역자치단체를, '박물관미술관구분' Index에 박물관미술관구분를 입력해주세요.
- 조건4: '광역' Index은 4-1문제와 같이 광역자치단체의 우선순위에 따라 나열해주세요.
- 조건5: 결과 DataFrame의 '어른관람료' Column(열)은 광역자치단체-박물관미술관구분별 평균 어른 관람료를, '어린이관람료' Column(열)은 광역자치단체-박물관미술관구분별 평균 어린이 관람료를, '관람료차이' Column(열)은 광역자치단체-박물관미술관구분별 평균 어른 관람료 - 평균 어린이 관람료(차액)을 입력해주세요.
- 어른/어린이 관람료 및 관람료차이는 평균값에서 소숫점 첫째 자리에서 반올림한 정수 값을 입력해주세요.
- 예시: 2978.5원 -> 2980.0원(소숫점 첫째 자리에서 반올림) -> 2980원(정수 값)
for idx, row in df_target7.iterrows():
if row['어린이관람료'] == 0 or row['어른관람료'] == 0:
df_target7 = df_target7.drop(idx, axis=0)
result = df_target7.pivot_table(index=['광역', '박물관미술관구분'], values=['어른관람료', '어린이관람료'], aggfunc='mean')
result['관람료차이'] = None
for idx, row in result.iterrows():
result.at[idx, '관람료차이'] = row['어른관람료'] - row['어린이관람료']
result2 = result.pivot_table(index=['광역', '박물관미술관구분'])
maxDif = result2[result2['관람료차이'] == result2['관람료차이'].max()]
minDif = result2[result2['관람료차이'] == result2['관람료차이'].min()]
result3 = pd.concat([maxDif, minDif], axis=0)
result3 = result3.round(-1).astype(int)
result3 = result3[['어른관람료', '어린이관람료', '관람료차이']]
result4 = result3.sort_index()
문제 4-4) 원하는 정보 얻기 04
- 가족(어른2, 청소년1, 어린이1)이 공휴일에 제주특별자치도 제주시에 있는 미술관을 관람하려 합니다. 총 관람료가 2만원 이하, 공휴일 4시간 이상 관람 가능한 미술관 list를 보여주세요.
- 조건1: 가족(어른 2명, 청소년 1명, 어린이 1명)의 총 관람료가 2만원 이하여야 합니다.
- 조건2: 제주특별자치도의 제주시에 있는 미술관을 가려고 합니다.
- 미술관: 이 Test에서는 df_target의 시설명 Column(열)에 있는 data 중 <'미술관' 또는 '갤러리' 또는 '아트'> 라는 글자들이 포함되어 있는 곳을 '미술관'이라고 정의합니다.
- 조건3: 공휴일에 가고자 합니다. 공휴일에 4시간 이상 관람 가능한 미술관이어야 합니다.
budget = 20000
df_jeju = df_target6[(df_target6['광역']=='제주특별자치도') & (df_target6['기초']=='제주시')]
df_jeju2 = df_jeju[df_jeju['시설명'].str.contains('미술관|갤러리|아트')]
df_jeju2 = df_jeju2[df_jeju2['공휴일관람가능시간']>=4]
df_jeju2 = df_jeju2[(df_jeju2['어른관람료']*2 + df_jeju2['청소년관람료'] + df_jeju2['어린이관람료'])<=20000]