본문 바로가기

Projects/기타

[파이썬] Plotly-Dash를 이용한 동적 대시보드

대시보드 사례

 

 

프로젝트 개요

  • 스터디 카페를 운영하는 지인이 있는데, 스터디 카페에 어떤 손님들이 방문하는지에 대해서 확인이 잘 안된다는 이야기를 들었다.
  • 키오스크에서 고객 결제 데이터를 확인할 수 있지만, 아래와 같이 각 결제내역별로 하나의 행으로 이루어져 있어서, 고객별, 일자별, 월별로 정리해서 한눈에 매출 추이나 경향을 파악하기 힘들다.
    이런 유형의 raw data가 있다.
  • 데이터를 가공, 수집하여 대시보드에서 신속하게 원하는 정보를 선별해서 볼 수 있도록 프로젝트를 진행해 보았다.
  • 주요 개발 프레임워크 : 파이썬, Flask, Pandas, Dash, Skeleton CSS, 

 

 

주요 코드

from typing import DefaultDict
import dash
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
import dash_html_components as html
import numpy as np
import plotly.graph_objs as go
import dash_bootstrap_components as dbc
import pandas as pd
from clean import clean_file, clean_file_crypt, get_MVPs, Cafe_user, empty_fig, user_stack_chart, df_property
import dash_table
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
import plotly.express as px
import time
import copy

app = dash.Dash(
    __name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}],
)
app.title = "OOO 스터디카페 OO점"
## default setup
colors = {'background':"#111111",'text':'#7FDBFF'}


################# creating variables

emptyfig = empty_fig()
myfile = '/home/pi/dash_examples/studycafe/data/raw.txt'
# clean_df = clean_file(myfile)
clean_df = clean_file_crypt(myfile)

# this is for getting the filters 
clean_property = df_property(clean_df)
clean_range = clean_property.monthdf.index


myfilters = {
    "type1":clean_property.type1,
    "type2":clean_property.type2,
    "type3":clean_property.type3,
    "type4":clean_property.type4,
    "type5":clean_property.type5,
    "user1":clean_property.user1,
    "user2":clean_property.user2,
    "user3":clean_property.user3,    
}



df = get_MVPs(clean_file(myfile))

testnumber = '01020780662'

testuser = Cafe_user(clean_df,testnumber)
def getdate():
    mydate = datetime.today().date()
    return mydate





## helper functions

def clean_file_crypt(file):
    # date 가져올 때 제대로 가져오기
    date_cols = ['일자']
    df = pd.read_csv(file,encoding='CP949',delimiter='\t',parse_dates=date_cols)

    ## 전화번호 뽑아내기

    df['전화번호'] = df['사용자ID'].str.extract(r'(\d{11})')
    df

    ## grouping하기 (유형)

    #1 추가사용요금 -> 추가사용요금
    #2 정액권 -> 정액권
    #3 1인석 -> 시간권
    #4 스터디룸, 예약권 -> 스터디룸
    #5 기간권 -> 기간권
    #6 사물함 -> 사물함

    f1 = df['좌석타입'].str.contains('추가사용요금', na=False)
    f2 = df['좌석타입'].str.contains('정액권', na=False)
    f3 = df['좌석타입'].str.contains('1인석', na=False)
    f4 = df['좌석타입'].str.contains('|'.join(['스터디룸','예약권']), na=False)
    f5 = df['좌석타입'].str.contains('기간권', na=False)
    f6 = df['좌석타입'].str.contains('사물함', na=False)

    df.loc[f1,'유형'] = '추가사용요금'
    df.loc[f2,'유형'] = '정액권'
    df.loc[f3,'유형'] = '시간권'
    df.loc[f4,'유형'] = '스터디룸'
    df.loc[f5,'유형'] = '기간권'
    df.loc[f6,'유형'] = '사물함'


    # 오류나는 행 지워주기 (관리자 행)
    df = df[~df['유형'].isna()]

    # 금액은 숫자로 바꿔주기 
    df['금액'] = df['금액'].str.replace(',','').astype(int).fillna(0)

    # 내가 필요한 행만 남기고 다 날리기

    df = df[['일자','유형','전화번호','금액']]

    # 최초결제일 열 추가를 위한 전처리, df2 임시
    df2 = df.loc[df.groupby('전화번호')['일자'].idxmin()]
    df2 = df2[['일자','전화번호']]
    df2.columns = ['최초결제일','전화번호']
    finaldf = df.merge(df2,on='전화번호',how='left')
    # 전화번호에 hypen 추가
    finaldf['전화번호'] =finaldf['전화번호'].str[:3] + "-" + finaldf['전화번호'].str[3:7] + "-" + finaldf['전화번호'].str[7:]

    test = finaldf['전화번호'].unique()
    newdf = pd.DataFrame()
    newdf['전화번호'] = test
    newdf['암호'] = ["XXX-XXXX-" + str(i).zfill(4) for i in newdf.index]
    newdf

    finaldf = finaldf.merge(newdf,on='전화번호',how='left')
    # 암호만 살리기
    finaldf = finaldf[['일자','유형','암호','금액', '최초결제일']]
    # 암호 이름 바꾸기 
    finaldf.columns = ['일자','유형','전화번호','금액', '최초결제일']


    # multiply rand
    finaldf['금액'] = finaldf['금액'] * randint(85,115) * 0.01 

    return finaldf


# output is list

class Cafe_user:
    def __init__(self, df, number):
        self.df = df
        self.number = number
    @property
    def first_date(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df['일자'].min()
    @property
    def first_date_str(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df['일자'].min().strftime('%y년 %m월 %d일')
    @property
    def last_date(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df['일자'].max()
    @property
    def last_date_str(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df['일자'].max().strftime('%y년 %m월 %d일')
    @property
    def last_item(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df.loc[df['일자'] == df['일자'].max(),'유형'].values[0]
    @property
    def last_amount(self):
        df =  self.df[self.df['전화번호'] == self.number]
        return df.loc[df['일자'] == df['일자'].max(),'금액'].values[0]

class df_property:
    def __init__(self, df):
        self.df = df
        # 결제유형 필터 : 전체, 기간권, 정액권, 시간권, 기타 순
        self.type1 = self.df['유형'].str.contains('|'.join(['']), na=False)
        self.type2 = self.df['유형'].str.contains('|'.join(['기간권']), na=False)
        self.type3 = self.df['유형'].str.contains('|'.join(['정액권']), na=False)
        self.type4 = self.df['유형'].str.contains('|'.join(['시간권']), na=False)
        self.type5 = self.df['유형'].str.contains('|'.join(['스터디룸', '추가사용요금', '사물함']), na=False)

        # 고객유형 필터 : 전체, 당월최초, 기존 순
        self.user1 = self.df['유형'].str.contains('|'.join(['']), na=False)
        self.user2 = ((self.df['최초결제일'].dt.year == self.df['일자'].dt.year) & (self.df['최초결제일'].dt.month == self.df['일자'].dt.month))
        self.user3 = ~ ((self.df['최초결제일'].dt.year == self.df['일자'].dt.year) & (self.df['최초결제일'].dt.month == self.df['일자'].dt.month))
        self.monthdf = self.df.resample("M",on='일자').sum()
    @property
    def num_user(self): 
        value = self.df['전화번호'].nunique()
        return f'{value:,.0f}'  # 천의 자리 comma 
    @property        
    def month_average(self):
        grand_total = self.df["금액"].sum()
        days = (self.df['일자'].max() - self.df['일자'].min()).days
        months_from_day = days / 30
        value = (grand_total / months_from_day) * 0.0001
        return f'{value:,.0f}'
    @property    
    def top10_portion(self):
        num_user = self.df['전화번호'].nunique()
        num_of_ten = round(num_user * 0.1)
        top10_sum = self.df.groupby('전화번호')['금액'].sum().sort_values(ascending=False).head(num_of_ten).sum()
        grand_total = self.df["금액"].sum()
        top10_portion =  top10_sum / grand_total
        return f'{top10_portion:.0%}'
    @property    
    def top10_month_average(self):
        days = (self.df['일자'].max() - self.df['일자'].min()).days
        months_from_day = days / 30
        num_user = self.df['전화번호'].nunique()
        num_of_ten = round(num_user * 0.1)
        top10_sum = self.df.groupby('전화번호')['금액'].sum().sort_values(ascending=False).head(num_of_ten).sum()
        value = (top10_sum / months_from_day) * ( 0.0001)
        return f'{value:,.0f}'




def empty_fig():
    fig = px.bar()
    fig.update_layout({'plot_bgcolor': 'rgba(0, 0, 0, 0)'})
    fig.update_xaxes(showgrid=False, zeroline=False, visible=False)
    fig.update_yaxes(showgrid=False, zeroline=False, visible=False)
    return fig



def user_stack_chart(df, user):
    df =  df[df['전화번호'] == user]
    # 월데이터로 바꾸기
    df.일자 =df.일자.dt.strftime('%Y-%m')
    fig = px.bar(df, x = '일자', y = '금액', color = 
    '유형', barmode = 'stack',color_discrete_sequence=px.colors.qualitative.G10)
    fig.update_layout({'plot_bgcolor': 'rgba(0, 0, 0, 0)'})
    fig.update_layout(xaxis_range=['2021-01-01','2021-08-31'])
    fig.update_layout(title=f"{user}님 상세",title_x=0.5)
    fig.update_traces(width=1000 * 3600 * 24 * 31*0.8)
    fig.update_xaxes(showgrid=False, zeroline=False, dtick="M1",tickformat="%b\n%Y")
    fig.update_yaxes(showgrid=False, zeroline=False, tickformat=',.0f')
    # fig.show(config={"displayModeBar": False, "showTips": False})
    return fig



def unixTimeMillis(dt):
    ''' Convert datetime to unix timestamp '''
    return int(time.mktime(dt.timetuple()))

def unixToDatetime(unix):
    ''' Convert unix timestamp to datetime. '''
    return pd.to_datetime(unix,unit='s')

def getMarks(myrange, Nth=100):
    ''' Returns the marks for labeling. 
        Every Nth value will be used.
    '''
    result = {}
    for i, date in enumerate(myrange):
        if(i%Nth == 1):
            # Append value to dict
            result[unixTimeMillis(date)] = str(date.strftime('%Y-%m-%d'))

    return result








####### Create global chart template

layout = dict(
    autosize=True,
    automargin=True,
    margin=dict(l=30, r=30, b=20, t=40),
    hovermode="closest",
    plot_bgcolor="#F9F9F9",
    paper_bgcolor="#F9F9F9",
    legend=dict(font=dict(size=10), orientation="h"),
    title="Satellite Overview",
    mapbox=dict(
        accesstoken=mapbox_access_token,
        style="light",
        center=dict(lon=-78.05, lat=42.54),
        zoom=7,
    ),
)






mytable = dash_table.DataTable(
    id='table1',
    columns=[{"name": i, "id": i,'type':'numeric', 'format': {'specifier': ',.0f'}}  if i not in ['전화번호'] else {"name": i, "id": i} for i in df.columns],
    data=df.to_dict('records'),
    style_as_list_view=True,
    style_cell={'padding': '2px'},
    page_action='none',
    style_table={'height': '600px', 'overflowY': 'auto'},
    style_cell_conditional=[
    {
        'if': {'column_id': '전화번호'},
        'textAlign': 'center'
    },
],
    style_data_conditional=[
    {
        'if': {'row_index': 0},
        'backgroundColor': '#FFF2CC',
    },
],
    style_header={
        'backgroundColor': 'white',
        'fontWeight': 'bold',
        'textAlign': 'center'
    },
)


user_graph = html.Div(dcc.Graph(
        id='user-graph',
        config={'displayModeBar':False},
        figure = emptyfig,
        ),
)
        


############# Create app layout
app.layout = html.Div(
    [
        dcc.Store(id="aggregate_data"),
        # empty Div to trigger javascript file for graph resizing
        html.Div(id="output-clientside"),
        html.Div(
            [
                html.Div(
                    [
                        html.Img(
                            src=app.get_asset_url("coffee-cup.png"),
                            id="plotly-image",
                            style={
                                "height": "60px",
                                "width": "auto",
                                "margin-bottom": "25px",
                            },
                        )
                    ],
                    className="one-third column",
                ),
                html.Div(
                    [
                        html.Div(
                            [
                                html.H2(
                                    "XXX OOO점",
                                    style={"margin-bottom": "0px"},
                                ),
                                html.H5(
                                    "Sales Overview", style={"margin-top": "0px"}
                                ),
                            ]
                        )
                    ],
                    className="one-half column",
                    id="title",
                ),
                html.Div(
                    [
                        html.A(
                            html.Button("서비스 문의", id="learn-more-button"),
                            href="https://dabid.tistory.com/",
                        )
                    ],
                    className="one-third column",
                    id="button",
                ),
            ],
            id="header",
            className="row flex-display",
            style={"margin-bottom": "25px"},
        ),
        html.Div(
            [
                html.Div(
                    [
                        html.P(
                            "기  간",
                            className="control_label",
                        ),
                        dcc.RangeSlider(
                            id="year_slider",
                            min = unixTimeMillis(clean_range.min()),
                            max = unixTimeMillis(clean_range.max()),
                            value = [unixTimeMillis(clean_range.min()),
                                    unixTimeMillis(clean_range.max())],
                            marks=getMarks(clean_range,3),
                            className="dcc_control",
                        ),
                        html.P("결제 유형", className="control_label"),
                        dcc.RadioItems(
                            id="type_select",
                            options=[
                                {"label": "전체 ", "value": "type1"},
                                {"label": "기간권", "value": "type2"},
                                {"label": "정액권", "value": "type3"},
                                {"label": "시간권", "value": "type4"},
                                {"label": "기타", "value": "type5"},

                            ],
                            value="type1",
                            labelStyle={"display": "inline-block"},
                            className="dcc_control",
                        ),
                        html.P("고객 유형", className="control_label"),
                        dcc.RadioItems(
                            id="user_select",
                            options=[
                                {"label": "전체", "value": "user1"},
                                {"label": "당월 최초", "value": "user2"},
                                {"label": "기존", "value": "user3"}
                            ],
                            value="user1",
                            labelStyle={"display": "inline-block"},
                            className="dcc_control",
                        ),
                        html.P("정렬기준", className="control_label"),
                        dcc.RadioItems(
                            id="sort_select",
                            options=[
                                {"label": "기간 합계 ", "value": "sort1"},
                                {"label": "최근 1개월", "value": "sort2"},
                                {"label": "최근 3개월", "value": "sort3"},
                            ],
                            value="sort1",
                            labelStyle={"display": "inline-block"},
                            className="dcc_control",
                        ),
                    ],
                    className="pretty_container four columns",
                    id="cross-filter-options",
                ),
                html.Div(
                    [
                        html.Div(
                            [
                                html.Div(
                                    [html.H6(id="usernum_text"), html.P("총 방문자 수")],
                                    id="mini1",
                                    className="mini_container",
                                ),
                                html.Div(
                                    [html.H6(id="sales_text"), html.P("월 평균 매출")],
                                    id="mini2",
                                    className="mini_container",
                                ),
                                html.Div(
                                    [html.H6(id="top10sales_text"), html.P("TOP 10%의 매출")],
                                    id="mini3",
                                    className="mini_container",
                                ),
                                html.Div(
                                    [html.H6(id="top10monthly_text"), html.P("TOP 10%의 \n 월평균 매출 ")],
                                    id="mini4",
                                    className="mini_container",
                                ),
                            ],
                            id="info-container",
                            className="row container-display",
                        ),
                        html.Div(
                            [dcc.Graph(id="count_graph",config={'displayModeBar':False})],
                            id="countGraphContainer",
                            className="pretty_container",
                        ),
                    ],
                    id="right-column",
                    className="eight columns",
                ),
            ],
            className="row flex-display",
        ),
        html.Div(
            [
                html.Div(
                    [mytable],
                    className="pretty_container seven columns",
                ),
                html.Div(
                    [
                        html.Div(
                            [
                                html.Div(
                                    [html.H6(id="first_date"), html.P("최초 방문일")],
                                    id="wells2",
                                    className="mini_container",
                                ),
                                html.Div(
                                    [html.H6(id="last_date"), html.P("최종 결제일")],
                                    id="gas2",
                                    className="mini_container",
                                ),
                                # html.Div(
                                #     [html.H6(id="last_type"), html.P("최종 결제 유형")],
                                #     id="oil2",
                                #     className="mini_container",
                                # ),
                                # html.Div(
                                #     [html.H6(id="last_amount"), html.P("최종 결제 금액")],
                                #     id="water2",
                                #     className="mini_container",
                                # ),
                            ],
                            id="info-container2",
                            className="row container-display",
                        ),
                        html.Div(
                            [user_graph],
                            id="countGraphContainer2",
                            className="pretty_container",
                        ),
                    ],
                    id="right-column2",
                    className="five columns",
                ),
            ],
            className="row flex-display",
        ),
        # html.Div(
        #     [
        #         html.Div(
        #             [dcc.Graph(id="pie_graph")],
        #             className="pretty_container six columns",
        #         ),
        #         html.Div(
        #             [dcc.Graph(id="aggregate_graph")],
        #             className="pretty_container six columns",
        #         ),
        #     ],
        #     className="row flex-display",
        # ),
    ],
    id="mainContainer",
    style={"display": "flex", "flex-direction": "column"},
)



# input is class Cafe_user output is html.div
def user_info(Cafe_user):
    # 0,1,2,3 순으로 첫번째 날짜, 마지막 날짜, 마지막 유형, 마지막  금액
    div = html.Div(
        [
            html.Div(f"{Cafe_user.number}  고객 정보",style= {'margin-bottom': '10px'}),
            html.Div(f"최초 방문일 : {Cafe_user.first_date_str()}"),
            html.Div(f"최종 결제일 : {Cafe_user.last_date_str()}"),
            html.Div(f"최종 결제 유형 : {Cafe_user.last_item()}"),
            html.Div(f"최총 결제 금액 : {Cafe_user.last_amount():,}")
        
        ],
        style= {'margin-bottom': '40px','font-size' : '18px','font-weight': '400'}
    )
    return div





#get aggregate date from filter
@app.callback(
    Output("aggregate_data", "data"),
    [
        Input("type_select", "value"),
        Input("user_select", "value"),
        Input("year_slider", "value"), # mydates is a lists , access with [0] and [1]
    ],
)
def update_sales_text(type_filter, user_filter, mydates):
    startdate = np.datetime64(mydates[0],'s')
    enddate = np.datetime64(mydates[1],'s')
    date_filter = (clean_df['일자'] >= startdate) & (clean_df['일자'] <= enddate)
    dff = clean_df[myfilters[type_filter] & myfilters[user_filter] & date_filter]
    data = df_property(dff)
    d1 = data.num_user
    d2 = data.month_average
    d3 = data.top10_portion
    d4 = data.top10_month_average
    return [d1,d2,d3,d4]




# get texts from aggregate_data
@app.callback(
    [
        Output("usernum_text", "children"),
        Output("sales_text", "children"),
        Output("top10sales_text", "children"),
        Output("top10monthly_text", "children"),
    ],
    [Input("aggregate_data", "data")],
)
def update_text(data):
    return data[0],data[1]+' 만원',data[2],data[3] +'만원'


# get first graph 





@app.callback(
    Output("count_graph", "figure"),
    [
        Input("type_select", "value"),
        Input("user_select", "value"),
        Input("year_slider", "value"), # mydates is a lists , access with [0] and [1]
    ],
)
def make_first_figure(type_filter, user_filter, mydates):
    startdate = np.datetime64(mydates[0],'s')
    enddate = np.datetime64(mydates[1],'s')
    # date_filter = (clean_df['일자'] >= startdate) & (clean_df['일자'] <= enddate)
    df = clean_df[myfilters[type_filter] & myfilters[user_filter]]
    myprop = df_property(df)
    monthdf = myprop.monthdf

    layout_count = copy.deepcopy(layout)

    colors = []
    for i in clean_range:
        if i >= startdate and i < enddate:
            colors.append("rgb(123, 199, 255)")
        else:
            colors.append("rgba(123, 199, 255, 0.2)")


    ## scatter dict은 왜있는지 모르겠음...
    data = [
        dict(
            type="bar",
            x=clean_range,
            y=monthdf["금액"],
            name="매출액(만원)",
            marker=dict(color=colors),
        ),
    ]

    layout_count["title"] = "월별 매출"
    layout_count["dragmode"] = "select"
    layout_count["showlegend"] = False
    layout_count["xaxis"] = {'type': 'date', 'tick0': clean_range[0], 'tickformat' : '%b\n%Y', 'tickmode': 'linear', 'dtick': 86400000.0 * 104} # 14 days
    layout_count["yaxis"] = {'tickformat' : ',.0f', 'tickmode' : 'auto','automargin': True} # 14 days
    
  
    figure = dict(data=data, layout=layout_count)
    return figure







@app.callback(
    Output('user-graph', 'figure'),
    Output("first_date", "children"), # 최초 방문
    Output("last_date", "children"), # 최종 결제
    Input('table1', 'active_cell'),
    State('table1', 'data')
)
def getActiveCell(active_cell, data):
    if active_cell:
        col = active_cell['column_id']
        row = active_cell['row']
        cellData = data[row][col]
        fig = user_stack_chart(clean_df, cellData)
        
        fig.update_layout(showlegend=False)
        myuser = Cafe_user(clean_df, cellData)
        t1 = myuser.first_date_str
        t2 = myuser.last_date_str
        # t3 = myuser.last_item
        # t4 = myuser.last_amount
        # t1 = f"{t1}"
        t2 = f"{t2}"
        # t3 = f"{t3}"
        # t4 = f"{t4:,}"

        return fig, t1,t2
    return emptyfig, "",""



def get_pivot(df):
    # duration_month = 6
    # 그 다음 피벗테이블
    df['연월']=df['일자'].dt.strftime('%y-%m')
    df2 = df.pivot_table(index='전화번호',columns='연월',values='금액',margins=True,aggfunc=np.sum).sort_values(by='All', ascending=False).head(100)
    df2 = df2.reset_index()
        # All column을 합계로 바꾸기
    df2.columns = df2.columns.str.replace('All', '합계')

    # 총 매출(월별)
    df2['전화번호'] = df2['전화번호'].str.replace('All', '총 매출(월)')
    pd.options.display.float_format = '{:,.0f}'.format

    return df2


#get aggregate date from filter
@app.callback(
    Output('table1', 'data'),
    Output('table1', 'columns'),
    [
        Input("type_select", "value"),
        Input("user_select", "value"),
        Input("year_slider", "value"), 
        Input("sort_select", "value"),# mydates is a lists , access with [0] and [1]
    ],
)
def update_sales_text(type_filter, user_filter, mydates, sorttype):
    startdate = np.datetime64(mydates[0],'s')
    enddate = np.datetime64(mydates[1],'s')
    date_filter = (clean_df['일자'] >= startdate) & (clean_df['일자'] <= enddate)
    dff = clean_df[myfilters[type_filter] & myfilters[user_filter] & date_filter]
    
    if sorttype == "sort1":
        df = get_pivot(dff)
        columns=[{"name": i, "id": i,'type':'numeric', 'format': {'specifier': ',.0f'}}  if i not in ['전화번호'] else {"name": i, "id": i} for i in df.columns]
        return df.to_dict('records'), columns
    elif sorttype == "sort2":
        df = get_pivot(dff)
        df = df.sort_values(df.columns[-2], ascending=False)
        columns=[{"name": i, "id": i,'type':'numeric', 'format': {'specifier': ',.0f'}}  if i not in ['전화번호'] else {"name": i, "id": i} for i in df.columns]
        return df.to_dict('records'), columns
    elif sorttype == "sort3":
        df = get_pivot(dff)
        months = (len(df.columns)-1) if len(df.columns) < 4 else 3
        newcolumn = f"{months} 개월 합계"
        mylist = [(-i-2) for i in range(0,months)] # months가 3이면 -2,-3,-4  2이면 -2,-3
        mycolumns = [df[df.columns[i]] for i in mylist]
        df[newcolumn] = sum(mycolumns)
        # 합계 열은 없애기
        df = df.drop(df.columns[-2],1)
        df = df.sort_values(df.columns[-1], ascending=False)
        columns=[{"name": i, "id": i,'type':'numeric', 'format': {'specifier': ',.0f'}}  if i not in ['전화번호'] else {"name": i, "id": i} for i in df.columns]
        return df.to_dict('records'), columns





if __name__ == '__main__':
    app.run_server(debug=True, host='0.0.0.0')
  • 코드가 너무 길어서 이 페이지에 모두 담아내기는 힘들 것 같다.

 

코멘트

 

  1. Class을 왜 사용하는지 항상 궁금해 했었는데, Class가 이렇게 유용할 수 있다는 점을 이번 프로젝트를 통해서 알게 되었다. 
    • 특정 고객에 대한 모든 사항을 Class 안에서 해결하니 코드를 직관적으로 관리할 수 있다.
    • 특정 기간, 필터에 대한 dataframe 및 거기서 파생되는 여러 속성도 하나의 Class 안에서 정리를 하다보니 중복되는 코드를 많이 방지할 수 있었다.
  2. Input이 String인 경우에 이를 활용하여 특정 함수를 호출하는 유용한 방법을 알게 되었고, 적용하게 되었다.
    • 만약 input이 'hello'라는 string인데, 실제로 나는 hello라는 변수를 parameter로 하는 함수를 호출하고 싶음
      → 먼저 dictionary를 만들어서 그 string을 내가 원하는 함수에 대응시키고, 이후에 dicitonary의 key(string)를 호출하는 방식으로 함수 호출