Iago Ferreiro Porto commited on
Commit
2bdb5f8
·
1 Parent(s): fe16a22

Inclusion de zona de confort

Browse files

Funciones de calculo de zona de confort y comprobacion de si las peliculas pertenecen a esta

backend/services/recommender_service.py CHANGED
@@ -252,6 +252,61 @@ def _puntuacion_preferencia_personal(
252
 
253
  return _similitud_con_historial(row, generos_referencia)
254
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  def recomendar_peliculas(
257
  emotion_es: str,
@@ -286,6 +341,13 @@ def recomendar_peliculas(
286
  generos_gustados = set(contador_generos_gustados.keys()) # Conjunto de generos que el usuario ha valorado positivamente
287
  tiene_historial = len(peliculas_vistas) > 0 # Indicador de si el usuario tiene historial de visualizacion
288
 
 
 
 
 
 
 
 
289
  peliculas_no_vistas = [row for row in movies_df if str(row.get("movieId", "")).strip() not in peliculas_vistas]
290
  # Si el usuario tiene historial, se recomienda entre las peliculas no vistas; si no tiene historial, se recomienda entre todas las peliculas.
291
  peliculas_candidatas = peliculas_no_vistas if peliculas_no_vistas else movies_df
@@ -309,32 +371,91 @@ def recomendar_peliculas(
309
  # Se seleccionan aleatoriamente del pool para fomentar diversidad entre usuarios sin historial, evitando recomendar siempre las mismas peliculas.
310
  return random.sample(candidate_pool, k=limit)
311
 
 
 
 
 
 
 
312
  is_positive = emotion_es in POSITIVE_EMOTIONS
313
 
314
  if is_positive:
315
  # Emocion positiva: mayor peso a novedad sin perder calidad.
316
- # La proporcion es 0.25 para preferencia personal, 0.30 para calidad global y 0.45 para diversidad
 
 
 
 
 
 
 
 
 
 
 
 
317
  ranked = sorted(
318
  peliculas_candidatas,
319
  key=lambda r: (
320
  0.25 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, generos_vistos)
321
  + 0.30 * _puntuacion_calidad_global(r, media_global_ratings)
322
- + 0.45 * (1.0 - _similitud_con_historial(r, generos_vistos))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  ),
324
  reverse=True,
325
  )
326
  else:
 
327
  # Emocion negativa: mayor peso a preferencia personal + similitud.
328
- # La proporcion es 0.55 para preferencia personal, 0.30 para calidad global y 0.15 para similitud con historial.
329
  reference_genres = generos_gustados if generos_gustados else generos_vistos
330
  ranked = sorted(
331
  peliculas_candidatas,
332
  key=lambda r: (
333
  0.55 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, reference_genres)
334
- + 0.30 * _puntuacion_calidad_global(r, media_global_ratings)
335
- + 0.15 * _similitud_con_historial(r, reference_genres)
 
336
  ),
337
  reverse=True,
338
  )
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  return ranked[:limit]
 
252
 
253
  return _similitud_con_historial(row, generos_referencia)
254
 
255
+ def _distancia_zona_confort(
256
+ row: dict,
257
+ zona_confort: set[str],
258
+ ranking_generos: dict[str, int],
259
+ ) -> float:
260
+ """
261
+ Funcion que calcula la distancia de una pelicula a la zona de confort del usuario
262
+ Si una pelicula contiene generos de zona comfort d = 0
263
+ En caso contrario, se queda con el genero de menor distancia
264
+
265
+ Args:
266
+ - row: pelicula de referencia a estudiar
267
+ - zona_confort: set con los dos generos mas vistos por el usuario
268
+ - ranking_generos: diccionario con los generos ordenados de mayor a menor por num de visualizaciones
269
+ Returns:
270
+ - Float que indica la distancia a la zona de confort de la pelicula
271
+ """
272
+
273
+ generos_pelicula = _obtener_generos_pelicula(row)
274
+ if not generos_pelicula or not ranking_generos:
275
+ return 1.0 # Si no hay generos asociados a la pelicula o ranking se devuelve la mayor d posible
276
+
277
+ # Si esta dentro de la zona de confort la distancia es cero
278
+ if generos_pelicula & zona_confort: # Se obtiene la interseccion de los dos conjuntos
279
+ return 0.0
280
+
281
+ # Si no esta dentro de la zona de confort se debe calcular la distancia
282
+ # Para cada genero de row se obtiene su pos en el ranking del usuario
283
+ ranking = [ranking_generos[g] for g in generos_pelicula if g in ranking_generos]
284
+ if not ranking:
285
+ return 1.0 # No hay ningun genero en comun
286
+ min_rank = min(ranking) # Se coje el genero mas proximo a la zona de confort
287
+ d = max(0, min_rank - 1) # Nos aseguramos de no tener distancias negativas
288
+ # Se normaliza a un valor entre cero y uno
289
+ max_posible = max(1, len(ranking_generos) - 1)
290
+ return d / max_posible
291
+
292
+ def _esta_zona_confort(row: dict, zona_confort: set[str]) -> bool:
293
+ """
294
+ Funcion que se encarga de comprobar si una pelicula pertenece o no a la zona de confort
295
+
296
+ Args:
297
+ - row: pelicula a comprobar si esta en el conjunto
298
+ - zona_confort: top 2 generos mas vistos del usuario
299
+ Returns:
300
+ - Boolean si la pelicula tiene generos dentro de la zona de confort del usuario
301
+ """
302
+ # Si no existe zona de confort la pelicula no puede estar en ella
303
+ if not zona_confort:
304
+ return False
305
+ else:
306
+ generos = _obtener_generos_pelicula(row)
307
+ # Se devuelve si existe interseccion de los dos conjuntos
308
+ return bool(generos & zona_confort)
309
+
310
 
311
  def recomendar_peliculas(
312
  emotion_es: str,
 
341
  generos_gustados = set(contador_generos_gustados.keys()) # Conjunto de generos que el usuario ha valorado positivamente
342
  tiene_historial = len(peliculas_vistas) > 0 # Indicador de si el usuario tiene historial de visualizacion
343
 
344
+ # Creacion de la zona de comfort (2 generos mas vistos)
345
+ # Se devuelve los generos ordenados por frecuencia usando most.common() de la clase Counter
346
+ top_generos = [g for g, _ in contador_generos_vistos.most_common()]
347
+ zona_confort = set[top_generos[:2] # La zona de confort son los dos generos mas visitados
348
+ # Se convierte el ranking a un diccionario donde la clave es el genero y el valor su posicion en el ranking
349
+ ranking_generos = {g:i for i, g in enumerate(top_generos)}
350
+
351
  peliculas_no_vistas = [row for row in movies_df if str(row.get("movieId", "")).strip() not in peliculas_vistas]
352
  # Si el usuario tiene historial, se recomienda entre las peliculas no vistas; si no tiene historial, se recomienda entre todas las peliculas.
353
  peliculas_candidatas = peliculas_no_vistas if peliculas_no_vistas else movies_df
 
371
  # Se seleccionan aleatoriamente del pool para fomentar diversidad entre usuarios sin historial, evitando recomendar siempre las mismas peliculas.
372
  return random.sample(candidate_pool, k=limit)
373
 
374
+ if tiene_historial and zona_confort:
375
+ candidatas_confort = [r for r in peliculas_candidatas if _esta_zona_confort(r, zona_confort)]
376
+ if candidatas_confort:
377
+ peliculas_candidatas = candidatas_confort
378
+
379
+ # En caso de SI HABER HISTORIAL se comprobara si la emocion es positiva o negativa
380
  is_positive = emotion_es in POSITIVE_EMOTIONS
381
 
382
  if is_positive:
383
  # Emocion positiva: mayor peso a novedad sin perder calidad.
384
+ # La proporcion es 0.25 para preferencia personal, 0.30 para calidad global, 0.20 para diversidad + 0.25 para d zona de confort
385
+ """ranked = sorted(
386
+ peliculas_candidatas,
387
+ key=lambda r: (
388
+ 0.25 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, generos_vistos)
389
+ + 0.30 * _puntuacion_calidad_global(r, media_global_ratings)
390
+ + 0.20 * (1.0 - _similitud_con_historial(r, generos_vistos))
391
+ + 0.25 * _distancia_zona_confort(r, zona_confort, ranking_generos)
392
+ ),
393
+ reverse=True,
394
+ )
395
+
396
+ # Ahora pasa a usar directamente la distancia a la zona de confort
397
  ranked = sorted(
398
  peliculas_candidatas,
399
  key=lambda r: (
400
  0.25 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, generos_vistos)
401
  + 0.30 * _puntuacion_calidad_global(r, media_global_ratings)
402
+ + 0.45 * _distancia_a_comfort_zone(r, comfort_genres, genre_rank) # más lejos del top2 => mejor
403
+ ),
404
+ reverse=True,
405
+ )"""
406
+
407
+ # 1) filtrar fuera del top2
408
+ outside = [r for r in peliculas_candidatas if not (_obtener_generos_pelicula(r) & comfort_genres)]
409
+
410
+ # fallback si el filtro deja vacío
411
+ base = outside if outside else peliculas_candidatas
412
+
413
+ # 2) ordenar por (más lejos del top2) y desempatar por calidad
414
+ ranked = sorted(
415
+ base,
416
+ key=lambda r: (
417
+ _distancia_a_top2(r, comfort_genres, genre_rank),
418
+ _puntuacion_calidad_global(r, media_global_ratings),
419
  ),
420
  reverse=True,
421
  )
422
  else:
423
+ """
424
  # Emocion negativa: mayor peso a preferencia personal + similitud.
425
+ # La proporcion es 0.55 para preferencia personal, 0.30 para calidad global, 0.05 para similitud con historial y 0.15 para zona confort
426
  reference_genres = generos_gustados if generos_gustados else generos_vistos
427
  ranked = sorted(
428
  peliculas_candidatas,
429
  key=lambda r: (
430
  0.55 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, reference_genres)
431
+ + 0.25 * _puntuacion_calidad_global(r, media_global_ratings)
432
+ + 0.05 * _similitud_con_historial(r, reference_genres)
433
+ + 0.15 * (1.0 - _distancia_zona_confort(r, zona_confort, ranking_generos)
434
  ),
435
  reverse=True,
436
  )
437
 
438
+ ranked = sorted(
439
+ peliculas_candidatas,
440
+ key=lambda r: (
441
+ 0.55 * _puntuacion_preferencia_personal(r, medias_rating_por_genero, reference_genres)
442
+ + 0.30 * _puntuacion_calidad_global(r, media_global_ratings)
443
+ + 0.15 * (1.0 - _distancia_a_comfort_zone(r, comfort_genres, genre_rank)) # más cerca del top2 => mejor
444
+ ),
445
+ reverse=True,
446
+ )"""
447
+
448
+ # 1) filtrar dentro del top2
449
+ inside = [r for r in peliculas_candidatas if _obtener_generos_pelicula(r) & zona_confort]
450
+
451
+ # fallback si el filtro deja vacío
452
+ base = inside if inside else peliculas_candidatas
453
+
454
+ # 2) ordenar por calidad global
455
+ ranked = sorted(
456
+ base,
457
+ key=lambda r: _puntuacion_calidad_global(r, media_global_ratings),
458
+ reverse=True,
459
+ )
460
+
461
  return ranked[:limit]