[편의점 매출 예측] Streamlit 페이지 구현 3. 메인 페이지 모델 적용 코드
개요
지난 포스팅에서 모델을 pkl로 models 폴더에 저장했다.
이번 포스팅에서는, 저장한 모델로 pages 폴더의 main page 구현 원리를 설명한다.
Streamlit
┣ 📂data
┣ 📂models
┃ ┣ 📜gm_model.pkl # 모델 pkl
┃ ┗ 📜ngm_model.pkl # 모델 pkl
┣ 📂pages
┃ ┣ 📜main_page.py # 메인 페이지
┃ ┗ 📜sub_page.py # 서브 페이지
┣ 📜Home.py # 첫 페이지
메인 페이지 기능
왼쪽의 회색 Side Bar 부분의 영역과 오른쪽 하얀 바탕의 Main 영역으로 나눠서 분류한다.
Python 코드 구현
라이브러리 및 데이터 불러오기
import streamlit as st
import joblib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly.subplots as sp
import plotly.express as px
import plotly.graph_objects as go
import seaborn as sns
import geopandas as gpd
from shapely.geometry import Polygon
import folium
import base64
import pyproj
import time
# 한글 폰트 설정
plt.rcParams['font.family'] = "AppleGothic"
# Windows, 리눅스 사용자
# plt.rcParams['font.family'] = "NanumGothic"
plt.rcParams['axes.unicode_minus'] = False
# 모델 불러오기
model1 = joblib.load("models/gm_model.pkl")
model2 = joblib.load("models/ngm_model.pkl")
# 데이터 프레임 불러오기
df = pd.read_csv('data/전체_수정_streamlit용.csv')
df1 = pd.read_csv('data/골목_streamlit용.csv')
df2 = pd.read_csv('data/비골목_streamlit용.csv')
Side Bar 영역
Side Bar > st.sidebar |
상권 선택 (상권 별로 타입 분류) > st.selectbox |
분기 선택 > st.selectbox |
- 상권 선택, 분기 선택 기능 구현
with st.sidebar:
# Select market
unique_market = df['상권_코드_명'].unique().tolist()
selected_feature1 = st.selectbox("상권을 선택하세요", unique_market)
# Select quarter
unique_quarter = ['1분기', '2분기', '3분기', '4분기']
selected_feature3 = st.selectbox("분기를 선택하세요!", unique_quarter)
상권 선택 : df 의 상권_코드_명의 unique 값을 리스트 형태로 저장 후, selectbox로 선택한 다음 select_feature1 에 저장한다.
분기 선택 : 분기를 selectbox로 선택한 다음 select_feature3 에 저장한다.
- 상권 타입 지정
# 상권 타입 지정
type = df[df['상권_코드_명'] == selected_feature1]['상권_구분_코드_명'].tolist()[0]
if type == '골목상권' :
type_code = 0 # 골목상권
else:
type_code = 1 # 비골목상권
selected_feature1 (상권 코드 명) 값의 상권구분코드가 '골목상권'이면 1,
아니면 0으로 나누어 모델이 개별 적용될 수 있도록 설정한다.
Main 영역
Main |
메인텍스트 영역 |
지도 영역 > map.html 불러오기 |
(예측 전 피쳐 가공 영역) > 예측 전, 배열 형태로 할당 |
예측 및 시각화 영역 > 개별 모델 예측 후 Plotly 시각화 |
- 메인 텍스트 영역
st.subheader('📊 강남구 편의점 매출 예측 서비스')
st.markdown('###### 좌측 사이드바에서 상권과 분기를 선택하면, 시간대별 예상 매출을 확인하실 수 있습니다')
st.caption('하단 지도에서 상권의 영역을 확인해보세요!👀')
- 지도 영역
with open('map.html', 'r', encoding='utf-8') as f:
html = f.read()
b64 = base64.b64encode(html.encode()).decode()
# Base64로 인코딩된 HTML을 출력
st.markdown(f'<iframe src="data:text/html;base64,{b64}" width=750 height=500></iframe>', unsafe_allow_html=True)
st.markdown('---')
map. html 파일을 markdown 형태로 출력하는 코드를 작성한다.
- 피쳐 가공 및 배열 생성
#시간대, 분기 값 리스트의 앞에 넣기
time1, time2, time3, time4, time5, quarter1, quarter2, quarter3 = 0, 0, 0, 0, 0, 0, 0, 0
if selected_feature3 == unique_quarter[0]:
quarter1 = 1
quarter2 = 0
quarter3 = 0
quarter_type = 1
elif selected_feature3 == unique_quarter[1]:
quarter1 = 0
quarter2 = 1
quarter3 = 0
quarter_type = 2
elif selected_feature3 == unique_quarter[2]:
quarter1 = 0
quarter2 = 0
quarter3 = 1
quarter_type = 3
else :
quarter1 = 0
quarter2 = 0
quarter3 = 0
quarter_type = 4
numeric_user_inputs = []
for i in range (6) :
user_input_i = [time1, time2, time3, time4, time5, quarter1, quarter2, quarter3]
numeric_user_input_i=[]
if i <= 4 :
user_input_i[i] = 1
if type_code == 0 :
filter_df = df1[(df1['상권_코드_명'] == selected_feature1) & (df1['분기'] == quarter_type) & (df1['기준_년_코드'] == 2022)]
else :
filter_df = df2[(df2['상권_코드_명'] == selected_feature1) & (df2['분기'] == quarter_type) & (df2['기준_년_코드'] == 2022)]
filter_list_i = filter_df.iloc[i, 7:].tolist()
user_input_i.extend(filter_list_i)
for value in user_input_i :
try:
numeric_value = float(value)
numeric_user_input_i.append(numeric_value)
# 예외처리
except ValueError:
st.error(f"입력값 '{value}'은(는) 숫자로 변환할 수 없습니다.")
numeric_user_inputs.append(numeric_user_input_i)
# 배열 생성
feature_array = np.array(numeric_user_inputs)
#st.write(feature_array)
sidebar 에서 입력한 피쳐들의 값을 리스트 형태로 예측값으로 할용해야 하므로, 사전 가공이 필요하다.
1. 예측에 사용했던 데이터 프레임은 시간대와 분기가 각각 원핫 인코딩되어있으므로, 데이터 프레임 형태로 재할당 한다.
2. 바깥쪽 for문
- 입력받은 side bar 값을 토대로, 2022년(수집일 기준 최신 데이터) 데이터를 기본값으로 만드는 filter_df을 만든다.
- time1 ~ 5, quarter1~3 의 값을 user_input_i에 넣어준다.
- filter_df 의 시간, 분기 피쳐들 자리를 제외한, 7행부터의 데이터 기본값들을 받아서 user_input_i 리스트에 append 해준다.
- 선택한 '상권_분류_명' 별로 골목상권 모델/비골목상권 모델인지에 따라 피쳐 개수가 달라지므로,[i, 7:] 이렇게 설정해주었다.
3. 안쪽 for 문
- user_input_i를 예측 시 에러가 나지 않도록, float 타입으로 변경해준다.
4. np.array 배열 형태 feature_array 에 저장한다.
- 예측 및 시각화 영역
##------------------------------------- 예측 -------------------------------------------
if type_code == 0:
predictions = model1.predict(feature_array)
else :
predictions = model2.predict(feature_array)
#st.write(predictions)
## 시각화
# 데이터 프레임으로 변경
df_predictions = pd.DataFrame({'예상 매출': predictions})
df_predictions.insert(0, '시간대', ['00 ~ 06', '06 ~ 11', '11 ~ 14', '14 ~ 17', '17 ~ 21', '21 ~ 24'])
# 정수로 변환
df_predictions['예상 매출'] = df_predictions['예상 매출'].astype(int)
# ---------------------------------- plotly 시각화 --------------------------------------
st.markdown(f"### {selected_feature1} {selected_feature3} 편의점 시간대별 예상 매출")
st.caption('👉 어느 시간대에 매출이 가장 높은지 확인해보세요!')
bar_trace = go.Bar(
x=df_predictions['시간대'],
y=df_predictions['예상 매출'],
text=[f'{val:,}' for val in df_predictions['예상 매출']],
textposition='inside',
texttemplate='%{text}',
)
layout = go.Layout(
xaxis_title='시간대',
yaxis_title='예상 매출'
)
bar_fig = go.Figure(data=[bar_trace], layout=layout)
st.plotly_chart(bar_fig)
max_type = df_predictions.loc[df_predictions['예상 매출'].idxmax()]['시간대']
st.markdown(f'#### 👉 시간대 {max_type} 의 매출이 가장 높습니다!')
st.markdown('---')
st.write('')
----------------------------------------------------------------------------------------
1. 예측 영역에서 Side Bar 영역에서 만들어준, type code를 활용해 개별 모델로 예측한다.
2.plotly의 Bar 차트를 이용해 시각화 한다.
3. markdown 문법을 이용해서 어떤 시간대가 가장 매출이 높은지 나타낸다.
전체 코드는 깃허브 참고(https://github.com/sin09135/MiniPJT)