Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
all_stopwords=['sugl', 'up', 'stetti', 'lei', 'vi', 'ti', 'avait', 'on', 'you', 'tutto', 'nos', 'ayantes', 'seront', 'avions', 'sta', 'farà', 'sulle', 'ad', 'soyez', 'own', 'étant', 'tutti', 'avevamo', 'its', 'son', 'facesse', 'en', 'nostro', 'farete', 'essendo', 'those', 'tua', "aren't", 'votre', 'avete', 'aveva', "don't", 'had', 'dagli', 'aren', 'again', 'queste', 'auras', 'avevo', 'sto', 'off', 'tra', 'stando', 'li', 'avesti', 'sia', 'between', 'fai', 'stiate', 'furono', 'once', 'nella', 'hai', 'ebbero', 'tu', 'avez', 'du', 'mais', 'eurent', 'eravate', 've', 't', 'facemmo', 'étants', 'sommes', 'stavate', 'y', 'be', 'degli', 'sarebbero', 'serons', 'eût', 'mie', 'siano', 'ayants', 'dalle', 'negl', 'avranno', 'mi', 'facevamo', 'having', 'farebbero', 'hadn', 'avresti', 'under', 'quante', 'fossimo', "she's", 'stanno', 'dagl', 'her', 'as', 'était', 'avec', 'until', 'ai', 'same', 'siate', 'been', 'avons', 'fummo', 'sera', 'eravamo', 'are', 'soyons', "didn't", 'con', 'sarei', 'dai', 'o', 'after', 'an', 'qu', 'ton', 'why', 'most', 'm', 'eûtes', 'fûmes', 'needn', 'of', 'dello', 'more', 'there', 'le', 'aveste', 'stessi', 'abbiano', 'starebbero', 'facesti', 'him', 'll', 'aurait', 'e', 'êtes', 'aviez', 'stettero', 'me', 'facendo', 'la', 'della', 'qui', 'vostro', 'fareste', 'étante', 'sono', 'dallo', 'agli', 'fosse', 'aient', 'erano', 'staranno', "needn't", 'few', 'dalla', 'tuoi', 'vostra', 'sarete', 'stavano', 'here', 'seraient', 'à', 'ayante', 'aurions', 'eut', 'stette', 'a', 'at', 'nel', 'te', "that'll", 'fût', 'avevi', 'just', 'who', 'toi', 'nell', 'than', 'ne', 'questa', 'sarò', 'eue', 'dov', 'haven', 'avuto', 'vostri', 'seriez', 'coi', 'themselves', 'aurez', 'or', 'abbiamo', 'eûmes', "haven't", "shouldn't", 'sont', 'fosti', 'each', 'eus', 'saremmo', 'not', 'auraient', 'doesn', 'au', 'about', 'no', "weren't", 'aurons', 'da', 'herself', "couldn't", 'the', 'mes', 'est', 'stareste', 'both', 'quanta', 'de', 'seras', 'ho', 'what', 'stia', 'weren', 'she', 'avremmo', 'les', 'avrà', 'siete', 'faremo', 'any', 'yourself', 'avais', 'out', 'sullo', 'des', 'si', 'étées', 'fussent', 'eussiez', 'so', 'che', 'abbia', 'que', 'lo', 'nor', 'abbiate', 'étée', 'farei', 'suoi', 'quella', 'has', 'only', 'contro', 'ours', 'myself', 'faccia', 'does', 'stessero', 'avremo', 'in', 'mia', 'avrebbe', 'while', 'which', 'wasn', "mightn't", 'saremo', 'ci', "doesn't", 'su', 'nous', 'fûtes', 'aie', 'suo', 'l', 'but', 'dans', 'fus', 'del', 'fusses', 'shan', 'un', 'j', 'other', 'avuti', 'avrete', 'col', 'avessimo', 'notre', 'gli', 'soit', 'dall', 'mightn', 'ain', 'it', 'did', 'faranno', 'se', 'facessero', 'et', 'non', 'starà', 'do', 'eri', 'am', 'into', 'sui', 'mustn', "mustn't", 'fusse', 'uno', 'sa', 'di', "you've", 'été', 'furent', 'eues', 'nostri', 'he', 'delle', 'è', 'for', 'auriez', 'anche', 'farebbe', 'itself', 'faremmo', 'vous', 'aura', 'étions', 'soient', 'if', 'chi', 'facessimo', 'miei', 'hanno', 'allo', 'sull', 'nelle', 'perché', 'facevo', 'sulla', 'tue', 'facessi', 'such', 'theirs', 'and', 'serez', 'eusses', 'facciano', 'sul', 'facciate', 'have', 'hasn', 'fu', 'should', 'was', 'sua', 'avrebbero', 'that', 'étais', 'nello', 'cui', 'stessimo', 'alle', 'dove', 'below', 'over', "should've", 'is', 'aux', 'serai', 'staremmo', 'pas', 'quanto', 'avevano', 'vos', 'ait', 'avute', 'al', 'avrai', 'per', 'faceva', 'faccio', 'noi', 'during', 'yours', 'étés', 'foste', 'siamo', 'against', 'faceste', 'auront', 'hers', 'fanno', 'io', 'then', 'staresti', 'fussions', "you'll", 'une', 'being', 'tes', 'this', "you'd", 'quanti', 'when', 'più', 'starai', 'through', 'didn', 'avaient', 'eu', 'steste', 'where', 'come', 'them', 'aies', 'couldn', "you're", 'faresti', 'i', 'facevate', "shan't", 'feci', 'with', 'questo', 'stai', 'saresti', 'stesse', 'my', 'era', 'fece', 'shouldn', 'avrò', 'fui', 'starei', 'can', 'starebbe', "isn't", 'leur', 'ta', 'fossero', 'sei', 'they', 'ebbi', 'stiano', "wasn't", 'avreste', 'ce', 'ayez', 'serais', 'yourselves', 'avesse', 'ed', 'sarà', "hadn't", 'je', 'eux', 'don', 'questi', 'stavi', 'stesti', 're', 'himself', 'es', 'stava', 'saranno', 'avevate', 'by', 'fossi', 'farò', 'their', 'all', 'fecero', 'stavo', 'nei', 'sarai', 'sue', 'because', 'elle', 'fut', 'degl', 'down', 'suis', 'avessero', 'dal', 'how', 'will', 'were', 'étaient', 'avuta', 'stavamo', 'ayant', 'eussions', 'voi', 'negli', "it's", 'wouldn', 'ils', 'n', 'too', 'serait', 'serions', 'ebbe', 'some', 'stiamo', 'staremo', 'stemmo', 'your', 'dei', 'ha', 'avendo', 'lui', 'starete', 'ont', 'dell', 'avessi', 'ces', 'mio', 'ourselves', 'very', 'tuo', 'quello', 'from', 'il', "wouldn't", 'sur', 'sois', 'fussiez', 'now', 'won', 'further', 'eusse', 'loro', 'quelli', 'we', 'c', 'these', 'ou', 'ayons', 'our', 'quale', 'sarebbe', 'agl', 'to', 'above', 'facevi', 'doing', 'alla', 's', 'pour', 'd', 'isn', 'par', 'ero', 'avemmo', 'sareste', 'facciamo', 'starò', 'before', 'his', 'ses', 'aurai', 'una', 'avrei', 'farai', 'nostra', 'quelle', 'sugli', 'facevano', 'eussent', 'aurais', 'whom', "hasn't", 'nostre', 'même', "won't", 'mon', 'vostre', 'moi', 'ma', 'étiez', 'étantes']
|
2 |
+
|
3 |
+
|
4 |
+
|
5 |
+
import streamlit as st
|
6 |
+
import scattertext as stx
|
7 |
+
from scattertext import CorpusFromPandas
|
8 |
+
from scattertext.WhitespaceNLP import whitespace_nlp
|
9 |
+
import pandas as pd
|
10 |
+
import nltk
|
11 |
+
from nltk.tokenize import word_tokenize
|
12 |
+
#from nltk.corpus import stopwords
|
13 |
+
import plotly.express as px
|
14 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
15 |
+
import matplotlib.pyplot as plt
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
################
|
20 |
+
#download function
|
21 |
+
import requests
|
22 |
+
import time
|
23 |
+
import pandas as pd
|
24 |
+
import urllib.parse
|
25 |
+
import datetime
|
26 |
+
|
27 |
+
from io import StringIO
|
28 |
+
|
29 |
+
|
30 |
+
def download_steam_api(app_id):
|
31 |
+
# Imposta il cursore iniziale
|
32 |
+
cursor = '*'
|
33 |
+
|
34 |
+
|
35 |
+
# Lista per raccogliere tutte le recensioni
|
36 |
+
reviews_data = []
|
37 |
+
|
38 |
+
|
39 |
+
while True:
|
40 |
+
# Parametri per la richiesta API
|
41 |
+
params = {
|
42 |
+
'filter': 'recent',
|
43 |
+
'language': 'all',
|
44 |
+
'review_type': 'all',
|
45 |
+
'purchase_type': 'all',
|
46 |
+
'num_per_page': 100,
|
47 |
+
'cursor': cursor,
|
48 |
+
'json': 1
|
49 |
+
}
|
50 |
+
|
51 |
+
# Genera la query string
|
52 |
+
query_string = urllib.parse.urlencode(params)
|
53 |
+
request_url = f'https://store.steampowered.com/appreviews/{app_id}?{query_string}'
|
54 |
+
|
55 |
+
print(f"Effettuo richiesta a: {request_url}")
|
56 |
+
st.write(f"Effettuo richiesta a: {request_url}")
|
57 |
+
|
58 |
+
# Effettua la richiesta
|
59 |
+
response = requests.get(request_url)
|
60 |
+
|
61 |
+
# Se la richiesta non va a buon fine, interrompi
|
62 |
+
if response.status_code != 200:
|
63 |
+
print("Errore nella richiesta:", response.status_code)
|
64 |
+
break
|
65 |
+
|
66 |
+
# Converte la risposta in JSON
|
67 |
+
data = response.json()
|
68 |
+
|
69 |
+
# Se non ci sono recensioni, esce dal ciclo
|
70 |
+
if not data.get('reviews') or len(data['reviews']) == 0:
|
71 |
+
print("Nessuna recensione trovata, fine ciclo.")
|
72 |
+
break
|
73 |
+
|
74 |
+
|
75 |
+
# Aggiorna il cursore per la prossima pagina
|
76 |
+
cursor = data.get('cursor')
|
77 |
+
|
78 |
+
|
79 |
+
# Processa ogni recensione
|
80 |
+
for review in data['reviews']:
|
81 |
+
# Conversione del timestamp in data formattata
|
82 |
+
timestamp = review.get('timestamp_created')
|
83 |
+
review_date = datetime.datetime.fromtimestamp(timestamp).strftime('%Y.%m.%d %H:%M') if timestamp else ''
|
84 |
+
|
85 |
+
|
86 |
+
# Costruisce il dizionario dei dati della recensione
|
87 |
+
reviews_data.append({
|
88 |
+
'Review ID': review.get('recommendationid'),
|
89 |
+
'Language': review.get('language'),
|
90 |
+
'Date Posted': review_date, # già convertito in formato "YYYY.MM.DD HH:MM" precedentemente
|
91 |
+
'Timestamp Updated': datetime.datetime.fromtimestamp(review.get('timestamp_updated')).strftime("%Y-%m-%d %H:%M:%S") if review.get('timestamp_updated') else '',
|
92 |
+
'Recommended': '1' if review.get('voted_up') else '0',
|
93 |
+
'Steam Purchase': '1' if review.get('steam_purchase') else '0',
|
94 |
+
'Weighted Vote Score': review.get('weighted_vote_score'),
|
95 |
+
'Votes Up': review.get('votes_up'),
|
96 |
+
'Votes Funny': review.get('votes_funny'),
|
97 |
+
'Comment Count': review.get('comment_count'),
|
98 |
+
'Received For Free': '1' if review.get('received_for_free') else '0',
|
99 |
+
'Written During Early Access': '1' if review.get('written_during_early_access') else '0',
|
100 |
+
'Primarily Steam Deck': '1' if review.get('primarily_steam_deck') else '0',
|
101 |
+
'Review Text': review.get('review'),
|
102 |
+
# Informazioni sull'autore
|
103 |
+
'Author SteamID': review.get('author', {}).get('steamid'),
|
104 |
+
'Num Games Owned': review.get('author', {}).get('num_games_owned'),
|
105 |
+
'Num Reviews': review.get('author', {}).get('num_reviews'),
|
106 |
+
'Playtime Forever': review.get('author', {}).get('playtime_forever'),
|
107 |
+
'Playtime Last Two Weeks': review.get('author', {}).get('playtime_last_two_weeks'),
|
108 |
+
'Playtime At Review': review.get('author', {}).get('playtime_at_review'),
|
109 |
+
'Last Played': datetime.datetime.fromtimestamp(review.get('author', {}).get('last_played')).strftime("%Y-%m-%d %H:%M:%S") if review.get('author', {}).get('last_played') else ''
|
110 |
+
})
|
111 |
+
|
112 |
+
|
113 |
+
# (Facoltativo) Attende un attimo per non sovraccaricare l'endpoint
|
114 |
+
time.sleep(1)
|
115 |
+
|
116 |
+
|
117 |
+
# Crea un DataFrame dai dati raccolti
|
118 |
+
df_reviews = pd.DataFrame(reviews_data)
|
119 |
+
|
120 |
+
return df_reviews
|
121 |
+
|
122 |
+
|
123 |
+
# Funzione per la tokenizzazione usando nltk e rimuovere le stopwords
|
124 |
+
def clean_text(text):
|
125 |
+
# Tokenizza il testo usando nltk
|
126 |
+
tokens = word_tokenize(str(text).lower())
|
127 |
+
|
128 |
+
# Rimuovi le stopwords dal testo
|
129 |
+
filtered_tokens = [word for word in tokens if word not in all_stopwords]
|
130 |
+
|
131 |
+
return ' '.join(filtered_tokens)
|
132 |
+
|
133 |
+
############################################ Interfaccia
|
134 |
+
|
135 |
+
# Interfaccia Streamlit
|
136 |
+
st.title("Steam Review Dashboard")
|
137 |
+
|
138 |
+
# Aggiungi un pulsante di reset
|
139 |
+
if st.button("Resetta Dashboard"):
|
140 |
+
# Ripristina sessione e variabili
|
141 |
+
st.session_state.clear()
|
142 |
+
st.experimental_rerun() # Ricarica la pagina per resettare tutto
|
143 |
+
|
144 |
+
|
145 |
+
# Carica o scarica le recensioni
|
146 |
+
app_id = st.text_input("Inserisci l'App ID di Steam (può impiegare alcuni minuti):", "")
|
147 |
+
|
148 |
+
if st.button("Scarica Recensioni (salva e poi ricarica il file)"):
|
149 |
+
if app_id:
|
150 |
+
df = download_steam_api(app_id)
|
151 |
+
st.session_state.df = df # Salva il DataFrame in session_state
|
152 |
+
st.write("Recensioni scaricate con successo!")
|
153 |
+
#st.write(df.head()) # Mostra le prime righe per il controllo
|
154 |
+
else:
|
155 |
+
st.error("Inserisci un App ID valido.")
|
156 |
+
|
157 |
+
# Carica un file CSV
|
158 |
+
uploaded_file = st.file_uploader("Carica un file CSV", type=["csv"])
|
159 |
+
|
160 |
+
if uploaded_file is not None:
|
161 |
+
df = pd.read_csv(uploaded_file)
|
162 |
+
st.session_state.df = df # Salva il DataFrame in session_state
|
163 |
+
#st.write("Prime righe del file caricato:", df.head())
|
164 |
+
|
165 |
+
# Mostra un'opzione per scaricare il DataFrame
|
166 |
+
def download_csv(df):
|
167 |
+
csv = df.to_csv(index=False)
|
168 |
+
st.download_button(
|
169 |
+
label="Scarica il CSV (fallo alla fine)",
|
170 |
+
data=csv,
|
171 |
+
file_name="steam_reviews.csv",
|
172 |
+
mime="text/csv"
|
173 |
+
)
|
174 |
+
|
175 |
+
# Pulsante per scaricare il CSV
|
176 |
+
if 'df' in locals():
|
177 |
+
df = st.session_state.df
|
178 |
+
download_csv(df)
|
179 |
+
|
180 |
+
|
181 |
+
|
182 |
+
|
183 |
+
##############à Messaggio
|
184 |
+
|
185 |
+
# Calcola il volume totale di recensioni
|
186 |
+
volume = len(df)
|
187 |
+
|
188 |
+
# Calcola il numero di recensioni raccomandate (1) e non raccomandate (0)
|
189 |
+
totpos = df['Recommended'].astype(int).sum() # Assumendo che 1 = raccomandato
|
190 |
+
totneg = volume - totpos # Il resto sono non raccomandate
|
191 |
+
|
192 |
+
# Calcola la percentuale di recensioni raccomandate e non raccomandate
|
193 |
+
percentpos = (totpos / volume) * 100
|
194 |
+
percentneg = (totneg / volume) * 100
|
195 |
+
|
196 |
+
# Trova le date minime e massime
|
197 |
+
mindate = df['Date Posted'].min()
|
198 |
+
maxdate = df['Date Posted'].max()
|
199 |
+
|
200 |
+
# Calcola il numero di recensioni per lingua
|
201 |
+
lingua_counts = df['Language'].value_counts()
|
202 |
+
primalingua = lingua_counts.index[0] if len(lingua_counts) > 0 else "N/D"
|
203 |
+
totperclingua1 = lingua_counts.iloc[0] if len(lingua_counts) > 0 else 0
|
204 |
+
secondalingua = lingua_counts.index[1] if len(lingua_counts) > 1 else "N/D"
|
205 |
+
totperclingua2 = lingua_counts.iloc[1] if len(lingua_counts) > 1 else 0
|
206 |
+
terzalingua = lingua_counts.index[2] if len(lingua_counts) > 2 else "N/D"
|
207 |
+
totperclingua3 = lingua_counts.iloc[2] if len(lingua_counts) > 2 else 0
|
208 |
+
|
209 |
+
# Format della frase
|
210 |
+
message = (f"Per il file caricato ho trovato {volume} recensioni, "
|
211 |
+
f"di cui il {percentpos:.2f}% raccomandate e il {percentneg:.2f}% non raccomandate, "
|
212 |
+
f"comprese tra {mindate} e {maxdate}, principalmente in {primalingua} ({totperclingua1}), "
|
213 |
+
f"{secondalingua} ({totperclingua2}) e {terzalingua} ({totperclingua3}).")
|
214 |
+
|
215 |
+
# Visualizza la frase
|
216 |
+
st.write(message)
|
217 |
+
|
218 |
+
|
219 |
+
|
220 |
+
################àà Filtro iniziale
|
221 |
+
|
222 |
+
df['Review Text'] = df['Review Text'].apply(lambda x: clean_text(x))
|
223 |
+
# Visualizza le prime righe del DataFrame
|
224 |
+
|
225 |
+
|
226 |
+
|
227 |
+
# Filtro iniziale per lingua
|
228 |
+
if 'language_filter' not in st.session_state:
|
229 |
+
st.session_state.language_filter = 'Tutti'
|
230 |
+
|
231 |
+
language_filter = st.selectbox("Seleziona la lingua o tutte le lingue", ['Tutti'] + df['Language'].unique().tolist(), index=0 if st.session_state.language_filter == 'Tutti' else df['Language'].unique().tolist().index(st.session_state.language_filter))
|
232 |
+
|
233 |
+
if language_filter != 'Tutti':
|
234 |
+
df_filtered = df[df['Language'] == language_filter]
|
235 |
+
st.session_state.language_filter = language_filter
|
236 |
+
else:
|
237 |
+
df_filtered = df
|
238 |
+
st.session_state.language_filter = 'Tutti'
|
239 |
+
|
240 |
+
|
241 |
+
#### Plot dataframe
|
242 |
+
|
243 |
+
# Ordina inizialmente per "Votes Up"
|
244 |
+
df_sorted = df_filtered.sort_values(by='Votes Up', ascending=False)
|
245 |
+
|
246 |
+
# Permetti all'utente di scegliere il criterio di ordinamento
|
247 |
+
sort_by = st.selectbox(
|
248 |
+
"Scegli la colonna per ordinare",
|
249 |
+
['Votes Up', 'Votes Funny', 'Comment Count', 'Num Games Owned'],
|
250 |
+
index=0
|
251 |
+
)
|
252 |
+
|
253 |
+
# Ordina il DataFrame in base alla scelta dell'utente
|
254 |
+
df_sorted = df_sorted.sort_values(by=sort_by, ascending=False)
|
255 |
+
|
256 |
+
# Mostra un selettore per quante righe visualizzare
|
257 |
+
num_rows = st.slider("Seleziona il numero di righe da visualizzare", min_value=5, max_value=50, value=5)
|
258 |
+
|
259 |
+
col_plot_sort = ['Review Text','Recommended','Date Posted','Language','Votes Up', 'Votes Funny', 'Comment Count', 'Num Games Owned','Last Played']
|
260 |
+
# Visualizza le prime righe del DataFrame ordinato e filtrato
|
261 |
+
st.write(f"Prime {num_rows} righe del file caricato (ordinate per {sort_by}):", df_sorted[col_plot_sort].head(num_rows))
|
262 |
+
|
263 |
+
|
264 |
+
##### **Pie Chart per la distribuzione delle recensioni (Raccomandato vs Non Raccomandato)**
|
265 |
+
st.subheader("Distribuzione delle Recensioni per Raccomandazione")
|
266 |
+
fig_pie = px.pie(df_filtered, names='Recommended', title='Distribuzione Raccomandazione delle Recensioni')
|
267 |
+
st.plotly_chart(fig_pie)
|
268 |
+
|
269 |
+
##### **Serie Temporale delle recensioni**
|
270 |
+
st.subheader("Serie Temporale delle Recensioni")
|
271 |
+
df_filtered['Date Posted'] = pd.to_datetime(df_filtered['Date Posted'], errors='coerce') # Assicurati che la colonna 'Date Posted' sia di tipo datetime
|
272 |
+
df_filtered['Date'] = df_filtered['Date Posted'].dt.date # Estrai solo la data (senza l'ora)
|
273 |
+
|
274 |
+
# Seleziona la granularità (giorno, mese, anno)
|
275 |
+
time_granularity = st.selectbox("Seleziona la granularità della serie temporale", ["Giorno", "Mese", "Anno"])
|
276 |
+
|
277 |
+
# Aggrega le recensioni in base alla granularità scelta
|
278 |
+
if time_granularity == "Giorno":
|
279 |
+
df_time_series = df_filtered.groupby('Date').size().reset_index(name='Recensioni')
|
280 |
+
elif time_granularity == "Mese":
|
281 |
+
df_filtered['Month'] = df_filtered['Date Posted'].dt.to_period('M')
|
282 |
+
df_filtered['Month'] = df_filtered['Month'].astype(str) # Converte Period in stringa
|
283 |
+
df_time_series = df_filtered.groupby('Month').size().reset_index(name='Recensioni')
|
284 |
+
else:
|
285 |
+
df_filtered['Year'] = df_filtered['Date Posted'].dt.year
|
286 |
+
df_time_series = df_filtered.groupby('Year').size().reset_index(name='Recensioni')
|
287 |
+
|
288 |
+
# Selezione per split Recommended vs Non Recommended
|
289 |
+
split_by_recommended = st.radio("Vuoi suddividere la serie temporale per Raccomandazione?", ('No', 'Sì'))
|
290 |
+
|
291 |
+
if split_by_recommended == 'Sì':
|
292 |
+
df_recommended = df_filtered[df_filtered['Recommended'] == 1]
|
293 |
+
df_not_recommended = df_filtered[df_filtered['Recommended'] == 0]
|
294 |
+
|
295 |
+
# Aggrega separatamente per "Recommended" e "Not Recommended"
|
296 |
+
if time_granularity == "Giorno":
|
297 |
+
df_recommended_ts = df_recommended.groupby('Date').size().reset_index(name='Recensioni')
|
298 |
+
df_not_recommended_ts = df_not_recommended.groupby('Date').size().reset_index(name='Recensioni')
|
299 |
+
elif time_granularity == "Mese":
|
300 |
+
df_recommended_ts = df_recommended.groupby('Month').size().reset_index(name='Recensioni')
|
301 |
+
df_not_recommended_ts = df_not_recommended.groupby('Month').size().reset_index(name='Recensioni')
|
302 |
+
else:
|
303 |
+
df_recommended_ts = df_recommended.groupby('Year').size().reset_index(name='Recensioni')
|
304 |
+
df_not_recommended_ts = df_not_recommended.groupby('Year').size().reset_index(name='Recensioni')
|
305 |
+
|
306 |
+
# Crea i grafici separati per Recommended e Not Recommended
|
307 |
+
fig_recommended = px.line(df_recommended_ts, x=df_recommended_ts.columns[0], y='Recensioni', title='Recensioni Raccomandate nel Tempo')
|
308 |
+
fig_not_recommended = px.line(df_not_recommended_ts, x=df_not_recommended_ts.columns[0], y='Recensioni', title='Recensioni Non Raccomandate nel Tempo')
|
309 |
+
|
310 |
+
st.plotly_chart(fig_recommended)
|
311 |
+
st.plotly_chart(fig_not_recommended)
|
312 |
+
else:
|
313 |
+
# Mostra la serie temporale aggregata per tutti
|
314 |
+
fig_time_series = px.line(df_time_series, x=df_time_series.columns[0], y='Recensioni', title='Numero di Recensioni nel Tempo')
|
315 |
+
st.plotly_chart(fig_time_series)
|
316 |
+
|
317 |
+
##################################################################################
|
318 |
+
|
319 |
+
|
320 |
+
# **Generazione dei barplot per le parole più frequenti**
|
321 |
+
st.subheader("Le 20 Parole più Frequenti nelle Recensioni")
|
322 |
+
|
323 |
+
# Impostazione dell'utente per decidere quante parole visualizzare
|
324 |
+
n_words = st.slider("Seleziona il numero di parole da visualizzare", min_value=5, max_value=50, value=20)
|
325 |
+
|
326 |
+
def counter_words(df_counter,n_words):
|
327 |
+
|
328 |
+
df_counter['Review Text'] = df_counter['Review Text'].astype(str)
|
329 |
+
|
330 |
+
# Filtro delle recensioni raccomandate (1) e non raccomandate (0)
|
331 |
+
df_counter['Recommended'] = df_counter['Recommended'].astype(str) # Converte in stringa
|
332 |
+
df_recommended = df_counter[df_counter['Recommended'] == '1']
|
333 |
+
df_not_recommended = df_counter[df_counter['Recommended'] == '0']
|
334 |
+
|
335 |
+
|
336 |
+
# Funzione per estrarre le parole più frequenti usando TfidfVectorizer
|
337 |
+
def get_most_common_words(text_data, n):
|
338 |
+
# Usa TfidfVectorizer per tokenizzare e contare le parole
|
339 |
+
#vectorizer = TfidfVectorizer(stop_words='english', max_features=n)
|
340 |
+
vectorizer = TfidfVectorizer(stop_words='english', max_features=n)
|
341 |
+
X = vectorizer.fit_transform(text_data)
|
342 |
+
word_freq = dict(zip(vectorizer.get_feature_names_out(), X.toarray().sum(axis=0)))
|
343 |
+
return word_freq
|
344 |
+
|
345 |
+
# Estrai il testo delle recensioni
|
346 |
+
text_recommended = df_recommended['Review Text'].dropna()
|
347 |
+
text_not_recommended = df_not_recommended['Review Text'].dropna()
|
348 |
+
|
349 |
+
print(text_recommended)
|
350 |
+
print(text_not_recommended)
|
351 |
+
|
352 |
+
# Ottieni le parole più frequenti
|
353 |
+
recommended_word_freq = get_most_common_words(text_recommended, n_words)
|
354 |
+
not_recommended_word_freq = get_most_common_words(text_not_recommended, n_words)
|
355 |
+
|
356 |
+
# Ordina le parole in ordine decrescente di frequenza
|
357 |
+
recommended_word_freq = dict(sorted(recommended_word_freq.items(), key=lambda item: item[1], reverse=True))
|
358 |
+
not_recommended_word_freq = dict(sorted(not_recommended_word_freq.items(), key=lambda item: item[1], reverse=True))
|
359 |
+
|
360 |
+
return recommended_word_freq,not_recommended_word_freq
|
361 |
+
|
362 |
+
recommended_word_freq,not_recommended_word_freq = counter_words(df_filtered,n_words)
|
363 |
+
|
364 |
+
# Crea i plot delle parole più frequenti
|
365 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
|
366 |
+
|
367 |
+
# Parole più frequenti per recensioni raccomandate (1)
|
368 |
+
ax = axes[0]
|
369 |
+
ax.bar(recommended_word_freq.keys(), recommended_word_freq.values())
|
370 |
+
ax.set_title("Parole più frequenti (Raccomandate)")
|
371 |
+
ax.set_xticklabels(recommended_word_freq.keys(), rotation=90, ha='center') # Etichette verticali
|
372 |
+
|
373 |
+
# Parole più frequenti per recensioni non raccomandate (0)
|
374 |
+
ax = axes[1]
|
375 |
+
ax.bar(not_recommended_word_freq.keys(), not_recommended_word_freq.values())
|
376 |
+
ax.set_title("Parole più frequenti (Non Raccomandate)")
|
377 |
+
ax.set_xticklabels(not_recommended_word_freq.keys(), rotation=90, ha='center') # Etichette verticali
|
378 |
+
|
379 |
+
# Mostra il grafico
|
380 |
+
st.pyplot(fig)
|
381 |
+
|
382 |
+
|
383 |
+
##################################################################################
|
384 |
+
|
385 |
+
# **Generazione di scattertext**
|
386 |
+
st.subheader("Analisi dei Termini più Frequente con Scattertext")
|
387 |
+
st.write('Se dovesse dare errore prova a selezionare una lingua')
|
388 |
+
|
389 |
+
def scatter_text(df_scat):
|
390 |
+
|
391 |
+
# Converte 'Recommended' in stringhe per garantire la compatibilità con scattertext
|
392 |
+
df_scat['Recommended'] = df_scat['Recommended'].astype(str) # Converte in stringa
|
393 |
+
|
394 |
+
|
395 |
+
|
396 |
+
df_scat = df_scat[df_scat['Recommended'].isin(['0', '1'])]
|
397 |
+
|
398 |
+
df_scat['Review Text'] = df_scat['Review Text'].fillna('').astype(str)
|
399 |
+
df_scat = df_scat.dropna(subset=['Review Text', 'Recommended'])
|
400 |
+
|
401 |
+
|
402 |
+
|
403 |
+
# Crea un Corpus usando scattertext con la funzione custom di tokenizzazione
|
404 |
+
corpus = CorpusFromPandas(df_scat, category_col='Recommended', text_col='Review Text',
|
405 |
+
nlp=whitespace_nlp).build()
|
406 |
+
|
407 |
+
# Usa scattertext per generare l'explorer
|
408 |
+
html = stx.produce_scattertext_explorer(
|
409 |
+
corpus,
|
410 |
+
category='1', # Categoria positiva (se '1' è "Recommended")
|
411 |
+
category_name='Recensioni Raccomandate', # Nome per la categoria positiva
|
412 |
+
not_category_name='Recensioni Non Raccomandate', # Nome per la categoria negativa
|
413 |
+
minimum_term_frequency=5, # Frequenza minima per i termini
|
414 |
+
width_in_pixels=700, # Larghezza del grafico in pixel
|
415 |
+
metadata=df_scat['Review Text'] # Metadati per il grafico
|
416 |
+
)
|
417 |
+
|
418 |
+
|
419 |
+
return html
|
420 |
+
|
421 |
+
html = scatter_text(df_filtered)
|
422 |
+
# Mostra il grafico HTML generato da scattertext
|
423 |
+
st.components.v1.html(html, height=800)
|
424 |
+
|
425 |
+
|
426 |
+
|
427 |
+
|
428 |
+
|
429 |
+
|
430 |
+
|