Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +98 -31
src/streamlit_app.py
CHANGED
|
@@ -4,18 +4,38 @@ from pathlib import Path
|
|
| 4 |
|
| 5 |
st.set_page_config(page_title="Customer Experience Analyzer", layout="wide")
|
| 6 |
|
| 7 |
-
|
| 8 |
-
st.write("Analyze customer sentiment from restaurant reviews.")
|
| 9 |
-
|
| 10 |
-
# Load the CSV from the same folder as this file
|
| 11 |
DATA_PATH = Path(__file__).parent / "reviews.csv"
|
| 12 |
df = pd.read_csv(DATA_PATH)
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
# KPIs
|
| 15 |
-
total_reviews = len(
|
| 16 |
-
positive_rate = (
|
| 17 |
-
negative_rate = (
|
| 18 |
|
|
|
|
| 19 |
col1, col2, col3 = st.columns(3)
|
| 20 |
col1.metric("Total Reviews", total_reviews)
|
| 21 |
col2.metric("Positive %", f"{positive_rate:.1f}%")
|
|
@@ -23,43 +43,90 @@ col3.metric("Negative %", f"{negative_rate:.1f}%")
|
|
| 23 |
|
| 24 |
# Chart
|
| 25 |
st.subheader("Sentiment Breakdown")
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
st.
|
| 30 |
-
selected_sentiment = st.sidebar.multiselect(
|
| 31 |
-
"Select sentiment",
|
| 32 |
-
options=df["sentiment"].unique(),
|
| 33 |
-
default=df["sentiment"].unique()
|
| 34 |
-
)
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
st.dataframe(filtered_df[["review_text", "sentiment"]])
|
| 41 |
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
st.subheader("Key Insights")
|
| 44 |
-
negative_df = filtered_df[filtered_df["sentiment"] == "negative"]
|
| 45 |
|
| 46 |
-
if
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
else:
|
| 52 |
-
st.write("No
|
| 53 |
|
| 54 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
st.subheader("Ask the Assistant")
|
|
|
|
| 56 |
question = st.text_input("Ask a question about the reviews")
|
| 57 |
|
| 58 |
if question:
|
| 59 |
q = question.lower()
|
|
|
|
| 60 |
if "positive" in q:
|
| 61 |
-
st.write("Positive reviews
|
| 62 |
elif "negative" in q:
|
| 63 |
-
st.write("Negative reviews
|
|
|
|
|
|
|
| 64 |
else:
|
| 65 |
-
st.write("This
|
|
|
|
| 4 |
|
| 5 |
st.set_page_config(page_title="Customer Experience Analyzer", layout="wide")
|
| 6 |
|
| 7 |
+
# Load data
|
|
|
|
|
|
|
|
|
|
| 8 |
DATA_PATH = Path(__file__).parent / "reviews.csv"
|
| 9 |
df = pd.read_csv(DATA_PATH)
|
| 10 |
|
| 11 |
+
# Title
|
| 12 |
+
st.title("Customer Experience Analyzer")
|
| 13 |
+
st.write("Analyze customer sentiment from restaurant reviews and identify where customer experience can improve.")
|
| 14 |
+
|
| 15 |
+
# Sidebar
|
| 16 |
+
st.sidebar.header("Filters")
|
| 17 |
+
|
| 18 |
+
selected_sentiment = st.sidebar.multiselect(
|
| 19 |
+
"Select sentiment",
|
| 20 |
+
options=df["sentiment"].unique(),
|
| 21 |
+
default=df["sentiment"].unique()
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
search_term = st.sidebar.text_input("Search reviews by keyword")
|
| 25 |
+
|
| 26 |
+
filtered_df = df[df["sentiment"].isin(selected_sentiment)]
|
| 27 |
+
|
| 28 |
+
if search_term:
|
| 29 |
+
filtered_df = filtered_df[
|
| 30 |
+
filtered_df["review_text"].str.contains(search_term, case=False, na=False)
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
# KPIs
|
| 34 |
+
total_reviews = len(filtered_df)
|
| 35 |
+
positive_rate = (filtered_df["sentiment"] == "positive").mean() * 100 if total_reviews > 0 else 0
|
| 36 |
+
negative_rate = (filtered_df["sentiment"] == "negative").mean() * 100 if total_reviews > 0 else 0
|
| 37 |
|
| 38 |
+
st.subheader("Overview")
|
| 39 |
col1, col2, col3 = st.columns(3)
|
| 40 |
col1.metric("Total Reviews", total_reviews)
|
| 41 |
col2.metric("Positive %", f"{positive_rate:.1f}%")
|
|
|
|
| 43 |
|
| 44 |
# Chart
|
| 45 |
st.subheader("Sentiment Breakdown")
|
| 46 |
+
if total_reviews > 0:
|
| 47 |
+
st.bar_chart(filtered_df["sentiment"].value_counts())
|
| 48 |
+
else:
|
| 49 |
+
st.warning("No reviews match the selected filters.")
|
| 50 |
|
| 51 |
+
# Example reviews
|
| 52 |
+
st.subheader("Example Customer Reviews")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
+
col4, col5 = st.columns(2)
|
| 55 |
|
| 56 |
+
positive_examples = filtered_df[filtered_df["sentiment"] == "positive"]
|
| 57 |
+
negative_examples = filtered_df[filtered_df["sentiment"] == "negative"]
|
|
|
|
| 58 |
|
| 59 |
+
with col4:
|
| 60 |
+
st.markdown("### Positive Review Example")
|
| 61 |
+
if len(positive_examples) > 0:
|
| 62 |
+
st.success(positive_examples.iloc[0]["review_text"])
|
| 63 |
+
else:
|
| 64 |
+
st.info("No positive review found for this selection.")
|
| 65 |
+
|
| 66 |
+
with col5:
|
| 67 |
+
st.markdown("### Negative Review Example")
|
| 68 |
+
if len(negative_examples) > 0:
|
| 69 |
+
st.error(negative_examples.iloc[0]["review_text"])
|
| 70 |
+
else:
|
| 71 |
+
st.info("No negative review found for this selection.")
|
| 72 |
+
|
| 73 |
+
# Key insights
|
| 74 |
st.subheader("Key Insights")
|
|
|
|
| 75 |
|
| 76 |
+
if total_reviews > 0:
|
| 77 |
+
if positive_rate > negative_rate:
|
| 78 |
+
st.write("Customer sentiment is mostly positive in the selected reviews.")
|
| 79 |
+
elif negative_rate > positive_rate:
|
| 80 |
+
st.write("Customer sentiment is mostly negative in the selected reviews.")
|
| 81 |
+
else:
|
| 82 |
+
st.write("Customer sentiment is evenly split between positive and negative.")
|
| 83 |
+
|
| 84 |
+
st.write(
|
| 85 |
+
f"""
|
| 86 |
+
- Out of **{total_reviews}** filtered reviews, **{positive_rate:.1f}%** are positive and **{negative_rate:.1f}%** are negative.
|
| 87 |
+
- This helps management quickly assess overall customer satisfaction.
|
| 88 |
+
- Searching by keyword can help identify specific issues such as service, food, or staff.
|
| 89 |
+
"""
|
| 90 |
+
)
|
| 91 |
+
else:
|
| 92 |
+
st.write("No insights available because no reviews match the selected filters.")
|
| 93 |
+
|
| 94 |
+
# Recommendations
|
| 95 |
+
st.subheader("Manager Recommendations")
|
| 96 |
+
|
| 97 |
+
if total_reviews > 0:
|
| 98 |
+
if negative_rate > 60:
|
| 99 |
+
st.warning("Customer dissatisfaction is high. Management should urgently review repeated complaints and investigate operational issues.")
|
| 100 |
+
elif negative_rate > 40:
|
| 101 |
+
st.info("Customer sentiment is mixed. Management should identify the most common negative themes and improve consistency.")
|
| 102 |
+
else:
|
| 103 |
+
st.success("Customer sentiment is mostly positive. Management should preserve strengths and monitor new complaints.")
|
| 104 |
else:
|
| 105 |
+
st.write("No recommendation available.")
|
| 106 |
|
| 107 |
+
# Reviews table
|
| 108 |
+
st.subheader("Filtered Reviews Table")
|
| 109 |
+
if total_reviews > 0:
|
| 110 |
+
st.dataframe(
|
| 111 |
+
filtered_df[["review_text", "sentiment"]].reset_index(drop=True),
|
| 112 |
+
use_container_width=True
|
| 113 |
+
)
|
| 114 |
+
else:
|
| 115 |
+
st.write("No reviews to display.")
|
| 116 |
+
|
| 117 |
+
# Assistant
|
| 118 |
st.subheader("Ask the Assistant")
|
| 119 |
+
|
| 120 |
question = st.text_input("Ask a question about the reviews")
|
| 121 |
|
| 122 |
if question:
|
| 123 |
q = question.lower()
|
| 124 |
+
|
| 125 |
if "positive" in q:
|
| 126 |
+
st.write("Positive reviews suggest that customers were satisfied with their restaurant experience.")
|
| 127 |
elif "negative" in q:
|
| 128 |
+
st.write("Negative reviews suggest that customers experienced problems that may affect satisfaction and loyalty.")
|
| 129 |
+
elif "improve" in q or "improvement" in q:
|
| 130 |
+
st.write("Management should focus on recurring negative feedback and investigate the causes behind poor customer experiences.")
|
| 131 |
else:
|
| 132 |
+
st.write("This dashboard helps management understand customer sentiment, review examples, and potential improvement areas.")
|