import faicons as fa import plotly.express as px import pandas as pd # بارگذاری داده‌ها from shared import app_dir, tips # فرض بر این است که tips از قبل DataFrame است from shinywidgets import render_plotly from shiny import reactive, render from shiny.express import input, ui # تبدیل احساس به عدد (تحلیل احساسات) tips["tip"] = tips["احساس"].map({"مثبت": 1, "خنثی": 0, "منفی": -1}) # تعیین بازه سنی bill_rng = (min(tips.سن), max(tips.سن)) # صفحه و سایدبار ui.page_opts(title="تحلیل احساسات کاربران", fillable=True) with ui.sidebar(open="desktop"): ui.input_slider( "سن", "رنج سنی", min=bill_rng[0], max=bill_rng[1], value=bill_rng, pre="سال" ) ui.input_checkbox_group( "تاریخ", "شبکه اجتماعی", tips["شبکه اجتماعی"].unique().tolist(), selected=tips["شبکه اجتماعی"].unique().tolist(), inline=True, ) ui.input_action_button("reset", "بازنشانی فیلتر") # آیکون‌ها ICONS = { "user": fa.icon_svg("user", "regular"), "wallet": fa.icon_svg("wallet"), "currency-dollar": fa.icon_svg("dollar-sign"), "ellipsis": fa.icon_svg("ellipsis"), } # باکس‌های آماری with ui.layout_columns(fill=False): with ui.value_box(showcase=ICONS["user"]): "تعداد کاربران" @render.express def total_tippers(): data = tips_data() ui.h3(f"تعداد کاربران: {data.shape[0]}") with ui.value_box(showcase=ICONS["wallet"]): "میانگین احساس" @render.express def average_tip(): data = tips_data() if data.shape[0] > 0: ui.h3(f"میانگین احساس مثبت یا منفی: {data['tip'].mean():.2f}") else: ui.h3("داده‌ای برای محاسبه میانگین وجود ندارد.") with ui.value_box(showcase=ICONS["currency-dollar"]): "میانگین سن" @render.express def average_bill(): data = tips_data() if data.shape[0] > 0: ui.h3(f"میانگین سن: {data['سن'].mean():.1f} سال") else: ui.h3("داده‌ای برای محاسبه میانگین سن وجود ندارد.") # نمودار و جدول with ui.layout_columns(col_widths=[6, 6, 12]): with ui.card(full_screen=True): ui.card_header("جدول داده‌ها") # رندر کردن داده‌ها در قالب جدول @render.data_frame def table(): return tips_data() # رندر کردن نمودار پراکندگی @render_plotly def scatterplot(): data = tips_data() if data.shape[0] == 0: return {} # بازگرداندن داده‌های خالی در صورت عدم وجود داده return px.scatter( data, x="سن", y="tip", color="جنسیت", trendline="lowess", labels={"tip": "امتیاز احساس", "سن": "سن"}, title="رابطه سن با احساس" ) with ui.card(full_screen=True): # عنوان برای تحلیل پراکندگی احساس with ui.card_header(class_="d-flex justify-content-between align-items-center"): "تحلیل پراکندگی احساس" # ایجاد یک Popover برای انتخاب متغیر گروه‌بندی with ui.popover(title="گروه‌بندی بر اساس متغیر"): ICONS["ellipsis"] ui.input_radio_buttons( "tip_perc_y", "گروه‌بندی بر اساس:", ["جنسیت", "تأثیر", "سطح تأثیر", "موضوع"], selected="جنسیت", inline=True, ) # رندر کردن نمودار ridgeplot @render_plotly def tip_perc(): from ridgeplot import ridgeplot dat = tips_data() if dat.shape[0] == 0: return {} # بازگرداندن داده‌های خالی در صورت عدم وجود داده dat["percent"] = dat["tip"] # استفاده از 'tip' به عنوان درصد احساس yvar = input.tip_perc_y() # دریافت انتخاب از کاربر uvals = dat[yvar].unique() # استخراج مقادیر یکتای متغیر انتخابی # ایجاد نمونه‌ها برای رسم نمودار ridgeplot samples = [[dat.percent[dat[yvar] == val]] for val in uvals] # ایجاد نمودار ridgeplot plt = ridgeplot( samples=samples, labels=uvals, bandwidth=0.01, colorscale="viridis", colormode="row-index", ) # تنظیمات نهایی برای نمودار plt.update_layout( legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5 ) ) return plt # بازگشت نمودار # اعمال CSS ui.include_css(app_dir / "styles.css") # -------------------------------------------------------- # واکنش‌ها # -------------------------------------------------------- @reactive.calc def tips_data(): سنی = input.سن() تاریخ_انتخابی = input.تاریخ() idx1 = tips["سن"].between(سنی[0], سنی[1]) idx2 = tips["شبکه اجتماعی"].isin(تاریخ_انتخابی) return tips[idx1 & idx2] @reactive.effect @reactive.event(input.reset) def _(): ui.update_slider("سن", value=bill_rng) ui.update_checkbox_group("تاریخ", selected=tips["شبکه اجتماعی"].unique().tolist())