세조목

최종 프로젝트 24일차(24.04.21) 본문

데이터 분석 공부/프로젝트

최종 프로젝트 24일차(24.04.21)

세조목 2024. 4. 21. 21:08

최종 프로젝트 24일차입니다.

금일은 Streamlit 작업을 진행했습니다.

Streamlit은 프론트엔드, 백엔드 지식이 없어도 간편하게

동적 웹사이트를 만들어서 배포할 수 있는 python 라이브러리입니다.

저희 우로보로스 팀은 네이버, 구글, 카카오에서 수집한 서울 소재 빵집 리뷰 데이터를 수집하여

리뷰별 특성 및 긍/부정 점수를 부여한 후 클러스터링 분석을 진행하여

그 결과를 바탕으로 검색 알고리즘을 구현하고 있습니다.

검색 알고리즘 구현 과정에서 단순히 python input 함수를 가지고서

출력값을 반환할수도 있지만 최종 프로젝트인만큼

완성도 높은 결과물을 만들고 싶었습니다.

시간 관계상 저희가 html, css, javascript와같은 웹 개발 언어를 가지고서

웹 사이트를 만들수는 없었기때문에 앞서 설명드린 Streamlit을 사용하여

검색 알고리즘이 구현된 웹 페이지를 만들기로했습니다.

 

어제 포스팅에서 UI작업을 진행했다고 말씀드렸는데요,

이 인터페이스를 Streamlit에서 구현하는 작업을 진행했습니다.

물론 어디서부터 어떻게 진행해야 할 지 전혀 감이 잡히지 않아서

일단 손에 잡히는 것부터 먼저 진행했는데요,

유튜브에 업로드되어있는 streamlit 기초 강의를 수강하고서

제목, 버튼 등을 위치시켰습니다.

import streamlit as st
import pandas as pd
import numpy as np
import pydeck as pdk

data = pd.DataFrame({
    'lat': np.random.randn(1) + 37.5643,  # 서울 중심부 근처
    'lon': np.random.randn(1) + 126.9779  # 서울 중심부 근처
})


if 'page' not in st.session_state:
    st.session_state.page = 'home'

# 1번 슬라이드
def home_page():
    title = "Hi Bread!"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """

    st.markdown(html_temp, unsafe_allow_html=True)  

    def check_input():
        if st.session_state.title == '서울 중구 소공동':
            st.session_state.page = 'sogong'
        elif st.session_state.title == '서울 중구 회현동':
            st.session_state.page = 'heohyeon'
        elif st.session_state.title == '서울 중구 명동':
            st.session_state.page = 'myeong'
        elif st.session_state.title == '서울 중구 필동':
            st.session_state.page = 'phil'
        elif st.session_state.title == '서울 중구 장충동':
            st.session_state.page = 'jangchung'
        elif st.session_state.title == '서울 중구 광희동':
            st.session_state.page = 'gwanghee'
        elif st.session_state.title == '서울 중구 을지로동':
            st.session_state.page = 'uljiro'
        elif st.session_state.title == '서울 중구 신당동':
            st.session_state.page = 'sindangdong'
        elif st.session_state.title == '서울 중구 다산동':
            st.session_state.page = 'dasan'
        elif st.session_state.title == '서울 중구 약수동':
            st.session_state.page = 'yaksu'
        elif st.session_state.title == '서울 중구 청구동':
            st.session_state.page = 'chungu'
        elif st.session_state.title == '서울 중구 신당동':
            st.session_state.page = 'sindang'
        elif st.session_state.title == '서울 중구 동화동':
            st.session_state.page = 'donghwa'
        elif st.session_state.title == '서울 중구 황학동':
            st.session_state.page = 'hwanhak'
        else:
            st.session_state.page = 'jungrim' 

    st.text_input(
        label='어디로 가고싶어?', 
        placeholder='예시) 서울 중구 OO동',
        key = 'title',
        on_change=check_input
    )

# 3번 슬라이드
def sogong():
    title = "어디에 사는 친구를 만나고 싶어?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

    col1, col2 = st.columns(2)

    with col1:
        # 위치 데이터 설정 (소공동의 위도 및 경도)
        sogong_location = [37.5643, 126.9779]
        
        # PyDeck을 사용하여 원 반경을 그리는 레이어 생성
        circle_layer = pdk.Layer(
            "ScatterplotLayer",
            data=pd.DataFrame([{'position': sogong_location}]),
            get_position='position',
            get_radius=300,  # 원의 반경을 미터 단위로 설정 (예: 300m)
            get_fill_color=[255, 0, 0, 140],  # 원의 색상과 투명도 설정 (RGBA)
            get_line_color=[0, 0, 0, 0],  # 선 색상 설정 (RGBA)
            get_line_width=0,  # 선 두께 설정
            radius_scale=1,
            radius_min_pixels=1,
            radius_max_pixels=100,
        )

        # PyDeck 뷰 설정
        view_state = pdk.ViewState(
            latitude=sogong_location[0],
            longitude=sogong_location[1],
            zoom=15,
            pitch=0,
        )

        # PyDeck 차트 생성
        r = pdk.Deck(
            layers=[circle_layer],
            initial_view_state=view_state,
            map_style='mapbox://styles/mapbox/light-v9',
        )

        st.pydeck_chart(r)

    with col2:
        if st.button('300m'):
            st.session_state.page = 'which_friend'
        if st.button('500m'):
            st.session_state.page = 'which_friend'
        if st.button('1km'):
            st.session_state.page = 'which_friend'
        if st.button('2km'):
            st.session_state.page = 'which_friend'

# 4번 슬라이드
def which_friend():
    title = "어떤 친구를 만나고 싶어?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)  

    button_style = """
    <style>
    .custom-button {
        display: inline-block;
        padding: 0.5em 1em;
        background-color: #000000;
        color: white;
        text-align: center;
        cursor: pointer;
        border: 2x solid #ffffff;
        border-radius: 4px;
        font-size: 16px;
        width: 150px;
        height: 50px;
    }
    .custom-button:hover {
    background-color: #242424;
    }
    </style>
    """

    st.markdown(button_style, unsafe_allow_html=True)

    col1, col2, col3, col4 = st.columns(4)

    with col1:
        st.markdown(f'<button class="custom-button">빵 맛집</button>', unsafe_allow_html=True)
        if st.button('빵 맛집'):
            st.session_state.page = "bread"
        st.markdown(f'<button class="custom-button">인테리어 맛집</button>', unsafe_allow_html=True)    
        if st.button('인테리어 맛집'):
            st.session_state.page = "interior"
    with col2:
        st.markdown(f'<button class="custom-button">분위기 맛집</button>', unsafe_allow_html=True)
        if st.button('분위기 맛집'):
            st.session_state.page = "atmosphere"
        st.markdown(f'<button class="custom-button">뷰 맛집</button>', unsafe_allow_html=True)
        if st.button('뷰 맛집'):
            st.session_state.page = "view"
    with col3:
        st.markdown(f'<button class="custom-button">사진 맛집</button>', unsafe_allow_html=True)
        if st.button('사진 맛집'):
            st.session_state.page = "photo"
        st.markdown(f'<button class="custom-button">건강 맛집</button>', unsafe_allow_html=True)
        if st.button('건강 맛집'):
            st.session_state.page = "health"
    with col4:
        st.markdown(f'<button class="custom-button">음악 맛집</button>', unsafe_allow_html=True)
        if st.button('음악 맛집'):
            st.session_state.page = "music"
        st.markdown(f'<button class="custom-button">친절 맛집</button>', unsafe_allow_html=True)
        if st.button('친절 맛집'):
            st.session_state.page = "friendly"

# 5번 슬라이드(빵 맛집)
def bread():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

    col1, col2, col3, col4 = st.columns(4)

    with col1:
        if st.button("a"):
            st.session_state.page = "a"
        if st.button("b"):
            st.session_state.page = "b"
    with col2:
        if st.button("c"):
            st.session_state.page = "c"
        if st.button("d"):
            st.session_state.page = "d"
    with col3:
        if st.button("e"):
            st.session_state.page = "e"
        if st.button("f"):
            st.session_state.page = "f"
    with col4:
        if st.button("g"):
            st.session_state.page = "g"
        if st.button("h"):
            st.session_state.page = "h"

# 5번 슬라이드(분위기 맛집)
def atmosphere():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

# 5번 슬라이드(사진 맛집)
def photo():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

# 5번 슬라이드(음악 맛집)
def music():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

# 5번 슬라이드(인테리어 맛집)
def interior():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)  

# 5번 슬라이드(뷰 맛집)
def view():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

# 5번 슬라이드(건강 맛집)
def health():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)

# 5번 슬라이드(친절 맛집)
def friendly():
    title = "누가 궁금해?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)



# 페이지 렌더링
if st.session_state.page == 'home':
    home_page()
# elif st.session_state.page == 'third_page':
#     third_page()
elif st.session_state.page == 'sogong':
    sogong()

elif st.session_state.page == 'which_friend':
    which_friend()

elif st.session_state.page == 'bread':
    bread()    

elif st.session_state.page == 'atmosphere':
    atmosphere()    

elif st.session_state.page == 'photo':
    photo()    

elif st.session_state.page == 'music':
    music()    

elif st.session_state.page == 'interior':
    interior()    

elif st.session_state.page == 'view':
    view()    

elif st.session_state.page == 'health':
    health()    

elif st.session_state.page == 'friendly':
    friendly()

Streamlit을 구현할 때 작성한 코드입니다.

 

제목 쓰고, 버튼 만드는 거는 구글에 정도보 많고, 유튜브에도 관련 영상들이 많아서

어렵지 않게 코드를 작성할 수 있었는데요,

문제는 '페이지 이동'이었습니다.

확인한 바로는 streamlit에서 직접적으로 페이지 이동 기능을 제공하고 있지 않기 때문에

사용자 정의 함수를 만들어서 페이지 이동 기능을 만들어야한다고 했습니다.

프로세스는 아래와 같습니다.

# 행정동 입력 時 해당 페이지로 넘어가는 함수
def check_input():
    if st.session_state.title == '서울 중구 소공동':
        st.session_state.page = 'sogong'
        
st.text_input(
    label='어디로 가고싶어?', 
    placeholder='예시) 서울 중구 OO동',
    key = 'title',
    on_change=check_input
        
# 버튼 클릭 時 다음 페이지로 넘어가는 함수
def sogong():
    title = "어디에 사는 친구를 만나고 싶어?"
    html_temp = f"""
    <div style="text-align: center;">
        <h1 style="color: white; font-size: 40px;">{title}</h1>
    </div>
    """
    st.markdown(html_temp, unsafe_allow_html=True)
    
    col1, col2 = st.columns(2)

    with col1:
        # 위치 데이터 설정 (소공동의 위도 및 경도)
        sogong_location = [37.5643, 126.9779]
        (생략)
    
    with col2:
        if st.button('300m'):
            st.session_state.page = 'which_friend'
        if st.button('500m'):
            st.session_state.page = 'which_friend'
        if st.button('1km'):
            st.session_state.page = 'which_friend'
        if st.button('2km'):
            st.session_state.page = 'which_friend'

# 함수 실행 조건문
if st.session_state.page == 'home':
    home_page()
elif st.session_state.page == 'sogong':
    sogong()

행정동 입력 時 해당 페이지로 넘어가는 함수와,

특정 버튼 클릭 時 다음 페이지로 넘어가는 함수를 먼저 만들어 두고,

전 단계 함수에 속해있는 조건(ex. st.session_state.page =='sogong')이 충족되면

함수 실행 조건문에 따라 다음 페이지로 넘어가는 로직입니다.

이 로직을 가지고서 다른 페이지로도 이동할 수 있기 때문에 페이지 이동 문제는 어느정도 해결이 된 것 같습니다.

 

또 다른 문제가 하나 더 있는데요,

저희가 구현하고자 하는 것은 검색 알고리즘이기때문에

사용자가 특정 키워드를 입력하면 그 키워드에 맞는 데이터만 필터가 되어서

사용자가 보고싶은 결과값만을 반환시켜야합니다.

그런데 저희가 가지고 있는 데이터셋에서 어떻게 값을 가져와야 할 지에대한 방법을 아직 찾지 못했습니다.

구글링 했을 때는 SQL DB에 데이터셋을 저장해두고,

SQL을 가지고서 데이터를 불러오는 식으로 진행해야한다고 적혀있기는 했는데

아직 방법을 찾고 있는 중이어서 내일 이어서 해당 작업 진행해야 할 것 같습니다.