Reporte Hito Final

Análisis de reviews de Amazon

Por: Cristian Alcarruz, Manuel Nova y Daniel Soto

Introducción

Al hacer compras en línea, es fácil notar que en la mayoría de los productos los reviews no son muy útiles. Es fácil imaginarse razones por la que esto podría ser. Por ejemplo, un usuario en específico podría ser muy crítico, y ponerle tres estrellas a un producto que cumplió perfectamente su propósito. Por otro lado, un usuario puede ser muy relajado, y evalúa un artículo con mayor puntaje del que debiese, pues se siente culpable a dar puntajes muy bajos. También podemos considerar el factor de que al entregar un rango de puntajes entre 1 y 5, la mayoría de los puntajes podrían tender a los extremos, pues es más directo evaluar un producto de manera binaria, que considerando los resultados generales.

Debido a esto, queremos realizar un análisis de los reviews de amazon para encontrar patrones en los mensajes anexados a los reviews, y generar medidas nuevas más fiables para evaluar productos. Junto a esto, también se puede hacer un análisis interesante de cómo varían los reviews de productos según variables como su precio o categoría. Los datos que se utilizarán fueron obtenidos por medio de Julian McAuley, un profesor asistente de la Universidad de California San Diego que distribuye estos datos con propósito de investigación y educación. Específicamente, se planea utilizar dos datasets, el de reviews y el de metadata de los productos.

En el presente reporte se entregarán los resultados más importantes del proyecto del curso Introducción a la minería de datos (código), junto a las hipótesis y conclusiones que se obtuvieron en cada hito.

Parte 1. Exploración de datos

Como se mencionó en la parte introductoria se utilizarán datos que fueron obtenidos por medio de Julian McAuley, (profesor asistente de la Universidad de California San Diego). Específicamente, se planea utilizar dos datasets, el de reviews y el de metadata de los productos. Para poder desarrollar lo anteriormente mencionado, se iniciará con una exploración de los dataset, para saber qué información(a grandes rasgos) contenían estos. Para esta instancia se utilizó el programa Rstudio.

# Variables usadas del dataset de reviews:
colnames(elecReviews)

#Variables usadas del dataset de metadata:
colnames(metaData)

De esta ejecución, se obtienen los nombres de las columnas de ambos dataset. Pero la idea es entender cómo se comportan los reviews de un producto en base a su precio, marca y categoría. Para esto, se graficó el precio promedio de los objetos, como se muestra a continuación:

ggplot(meanReviews, aes(x = overall)) + 
  ggtitle("Puntajes promedio de los productos") +
  xlab("Puntaje") + 
  ylab("Número de productos") +
  geom_histogram(binwidth=0.2)

WhatsApp%20Image%202018-08-24%20at%204.58.29%20PM.jpeg

Inmediatamente se puede notar debilidades en el sistema de reviews de amazon, pues cerca del 70% de los productos tienen puntajes de sobre 4 estrellas. Esto se ve mostrado a continuación, donde se aprecia la distribución de los cuartiles en cuanto a estrellas.

quantile(meanReviews$overall, c(0, 0.3, 0.7, 1.0))

Luego se continúa con la distribución estándar de estos promedios:

ggplot(stdReviews, aes(x = overall)) + 
  ggtitle("Desviaciones estándar de los reviews de cada producto") +
  xlab("Desviación estándar") + 
  ylab("Número de productos") +
  geom_histogram(binwidth=0.03)

WhatsApp%20Image%202018-08-24%20at%204.59.13%20PM.jpeg

Se puede ver que este gráfico se ve un poco más saludable, con una distribución similiar a una gaussiana. A pesar de esto, se sigue teniendo el problema donde la desviación es 0, es decir, todos los reviewers evalúan con el mismo puntaje a un producto. Esto complica distinguir entre la calidad de distintos productos, aunque el contenido de los reviews pudiese ser mucho más útil.

Luego, analizamos la cantidad de reviews por producto:

ggplot(countReviews, aes(x = overall)) +
  scale_x_continuous(trans = "log10") +
  scale_y_continuous(trans = "log10") +
  ggtitle("Número de reviews por producto") +
  xlab("Número de reviews [log10(n)]") +
  ylab("Cantidad de ítems [log10(n)]") +
  geom_histogram(binwidth=0.15)

WhatsApp%20Image%202018-08-24%20at%204.59.35%20PM.jpeg

Cabe destacar que este gráfico usa escalas logarítmicas en ambos ejes. Como se puede ver que es comportamiento casi lineal en este histograma, entre la cantidad de reviews y la frecuencia, entonces se puede concluir que la mayoría de los productos, tienen la menor cantidad de reviews, y muy pocos productos tienen una gran cantidad de reviews. Debido a esto, la calidad de productos con muy distintas cantidades de reviews se vuelve menos fiable de comparar con las medidas existentes. Por lo tanto en alguna iteración posterior podría existir algún tipo de normalización entre estas medidas.

Por último, se buscan las tendencias en el puntaje promedio de cada producto en referencia a su precio, utilizando el siguiente código:

priceReviews$color <- ifelse(priceReviews$overall %% 1 == 0, "Entero", "Decimal")

ggplot(priceReviews, aes(x = price, y = overall)) +
  ggtitle("Distribución de puntajes por precio") +
  ylab("Puntaje") +
  xlab("Precio") + 
  geom_point(aes(colour = color))

WhatsApp%20Image%202018-08-24%20at%205.00.01%20PM.jpeg

El gráfico anterior se observa que el resultado es coherentes con nuestro histograma anterior de la distribución de puntajes, pero además se observar que en productos de mayor precio hay reviews más altos. Además se notan leves tendencias a los puntajes enteros, es decir, 2, 3, 4 y 5. Esto permite concluir algo similar a lo visto con la desviación estándar de los puntajes de productos. Existen muchos que tienen una desviación igual a 0, y por lo tanto el puntaje se mantiene en un entero y se vuelve poco descriptivo.

Dada estas distribuciones con sus desviaciones, el grupo propone estudiar y/o una medida que utilice el contenido textual de los reviews para entregar un resultado más descriptivo y fiable.

Primeras Conclusiones

El resumen del estudio de la exploración de datos se puede ver claramente que los reviews están naturalmente sesgados a valores más altos, la gran mayoría estando sobre las 3.5 estrellas. Además se puede notar la tendencia a los valores enteros, llevando a la medida ser poco expresiva

Luego de haber generado la exploración de datos, se formuló la hipótesis “Generar una medida sobre los reviews y el puntaje asignado, menos sesgada; evitando la falta de varianza de estos mismos.

Parte 2. Análisis sentimental de reviews en amazon

Para verificar la validez de las hipótesis planteadas en la sección anterior, se realizó un análisis sentimental de los reviews en amazon, para posteriormente realizar un clustering sobre los resultados obtenidos. En cada uno de estos clusters, se analizará las estadísticas de los puntajes de reviews contenidos en cada uno.

In [1]:
import numpy as np
import pandas as pd
from pprint import pprint

data = pd.read_json("Electronics_metadata.json")
data["reviewSentiments"] = ""

data.head()
Out[1]:
asin helpful overall reviewText summary reviewSentiments
0 0594481813 [2, 2] 4 This item is just as was described in the orig... As expected
1 0594481813 [0, 0] 5 bought for a spare for my 9&#34; Nook HD and i... great fit
2 0594481813 [1, 1] 5 My son crewed my HD charger cord so I needed a... Works Great
3 0594481813 [0, 1] 3 This is a good beefy 2 amp charger, but it cov... It Works
4 0594481813 [2, 2] 5 I lost my B&N original cable. I looked around... Great replacement for original power cable

Para el análisis sentimental, se utilizó el método VADER0, que entrega resultados buenos, incluso para oraciones con palabras de connotación negativa, usadas con significados positivos, en las que otros modelos de análisis sentimental no logran extraer el sentimiento correctamente.

Lamentablemente tenemos un problema por el tamaño del dataset, dado que son muchos productos, y aún más reviews.

In [2]:
data.size
Out[2]:
6370344

Dado el tamaño del dataset mostrado anteriormente, se realizó un análisis con un número reducido de reviews. Comenzaremos analizando una distribución aleatoria de 2500 elementos del dataset.

In [3]:
reduced_data = data.sample(n=2500)

print("Promedio original: {:2.4f}\nDesviacion estándar original: {:2.4f}\n".format(data['overall'].mean(),
                                                                                 data['overall'].std()))
print("Promedio reducido: {:2.4f}\nDesviación estándar reducida: {:2.4f}\n".format(reduced_data['overall'].mean(),
                                                                                 reduced_data['overall'].std()))

import matplotlib.pyplot as plt

bins = np.linspace(0.5,5.5,6)

plt.hist(data['overall'], bins=bins, alpha=0.5, label='original', density=True)
plt.hist(reduced_data['overall'], bins=bins, alpha=0.5, label='reduced', density=True)
plt.legend(loc='upper left')
plt.show()
Promedio original: 4.2251
Desviacion estándar original: 1.1846

Promedio reducido: 4.2560
Desviación estándar reducida: 1.1640

<Figure size 640x480 with 1 Axes>

Como se puede ver, el dataset reducido mantiene bastante bien el promedio y desviación estándar de los datos originales, por lo que se considera una buena generalización realizar el análisis sobre este dataset reducido.

In [4]:
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from nltk import sent_tokenize

scores=[]
sia = SentimentIntensityAnalyzer()

for review in reduced_data["reviewText"]:
    review_scores = []
    for sentence in sent_tokenize(review):
        ss = sia.polarity_scores(sentence)
        review_scores.append(ss['compound'])
    scores.append(sum(review_scores) / float(max(len(review_scores), 1)))
    
reduced_data["reviewSentiments"] = scores
/usr/local/lib/python3.6/dist-packages/nltk/twitter/__init__.py:20: UserWarning: The twython library has not been installed. Some functionality from the twitter package will not be available.
  warnings.warn("The twython library has not been installed. "

De los puntajes obtenidos se grafica la densidad de los datos, para ver si es posible hacer una clusterización interesante sobre los datos.

In [5]:
import seaborn as sns

sns.set_style('whitegrid')
sns.kdeplot(reduced_data["reviewSentiments"], bw=0.5)
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fcf7fc36978>

Se puede ver que el resultado es claramente unimodal, y que no es muy útil hacer clustering sobre él. Aún así, se analizará la densidad de estos puntajes; agrupando los reviews por puntaje asignado, para ver si el patrón se mantiene.

In [6]:
for i in range(1,6):
    sns.kdeplot(reduced_data[reduced_data["overall"] == i]["reviewSentiments"],
                bw=0.5, 
                label="{} estrella{}".format(i, "" if i == 1 else "s"))

A primera vista, estos resultados nos dicen que los usuarios reflejan bien sus sentimientos en el puntaje asignado, lo que nos tentaría a modificar nuestra hipótesis. Pero al mirar más detalladamente la densidad para cada estrella podemos distinguir algo bastante interesante.

In [7]:
plt.figure(figsize=(15,8))

ax1 = plt.subplot2grid((2,6), (0,0), colspan=2)
ax2 = plt.subplot2grid((2,6), (0,2), colspan=2)
ax3 = plt.subplot2grid((2,6), (0,4), colspan=2)
ax4 = plt.subplot2grid((2,6), (1,1), colspan=2)
ax5 = plt.subplot2grid((2,6), (1,3), colspan=2)

axs = [ax1, ax2, ax3, ax4, ax5]
colors = ["b", "darkorange", "g", "r", "m"]

for i in range(1,6):
    sns.kdeplot(reduced_data[reduced_data["overall"] == i]["reviewSentiments"],
                bw=0.5, 
                label="{} estrella{}".format(i, "" if i == 1 else "s"),
                color = colors[i-1],
                ax = axs[i-1])

plt.tight_layout()
In [8]:
boxes = [reduced_data[reduced_data["overall"] == i]["reviewSentiments"] for i in range(1,6)]

plt.figure(figsize=(15,5))
bp = plt.boxplot(boxes,
                 labels = ["1 estrella", "2 estrellas", "3 estrellas", "4 estrellas", "5 estrellas"],
                 patch_artist=True)

plt.gca().set_ylim(-1,1)
for i in range(1,6):
    bp['boxes'][i-1].set(color='k')
    bp['boxes'][i-1].set(facecolor=colors[i-1])
    bp['medians'][i-1].set(color='k')
    bp['fliers'][i-1].set(marker='o', alpha=0.075)
    
plt.show()
/usr/local/lib/python3.6/dist-packages/numpy/core/fromnumeric.py:52: FutureWarning: reshape is deprecated and will raise in a subsequent release. Please use .values.reshape(...) instead
  return getattr(obj, method)(*args, **kwds)

De los gráficos mostrados anteriormente se puede extraer que en los reviews de 1, 2 y 4 estrellas, un pequeño grupo de mayor densidad con sentimientos mayores al resto del conjunto. Además, en los reviews de 4 estrellas, se puede ver otro grupo más pequeño donde los sentimientos sugerirían puntajes más bajos del entregado.

Segundas Conclusiones

Con esta información se comprueba nuestras hipótesis sobre los datos, pero un poco refinadas, sabiendo que los conjuntos donde se observa mayor disparidad son aquellos donde el producto tiene 1, 2 o 4 estrellas.

Concluimos de esta segunda parte presentando nuestra nueva hipótesis: Se pueden encontrar los productos que tienen un puntaje en uno de estos grupos sesgados, y pueden ser refinados usando el sentimiento del usuario para colocarlos en una clase más adecuada

Parte 3. Clustering

Como se mencionó en la sección anterior se reduce el dataset para poder trabajar con el; se utilizaron 2500 datos. Además se muestra el promedio y la desviación estándar de cada dataset, el original y el reducido; donde se aprecia claramente que el dataset mantiene la propiedades del original, variando muy poco.

Con el dataset ya filtrado; se realiza el análisis sentimental de los reviews con el método Vader, como se mencionó en la sección anterior.

In [9]:
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from nltk import sent_tokenize

scores=[]
sia = SentimentIntensityAnalyzer()

for review in reduced_data["reviewText"]:
    review_scores = []
    for sentence in sent_tokenize(review):
        ss = sia.polarity_scores(sentence)
        review_scores.append(ss['compound'])
    scores.append(sum(review_scores) / float(max(len(review_scores), 1)))
    
reduced_data["reviewSentiments"] = scores

A continuación se presenta un gráfico del resultado de aplicar el análisis sentimental a los reviews, donde se tienen los distintos puntajes y sus varianzas con respectos al valor central que se está estudiando.

In [10]:
plt.scatter(reduced_data["overall"], reduced_data["reviewSentiments"])
plt.show()

Del gráfico mostrado se desprende que que los sentimientos de los reviews ya no están todos centrados en el valor entero como se observó el la exploración de datos, entonces se destaca que la hipótesis planteada en la sección anterior se cumple.

Para un mejor analisis se realizará K-means....

In [11]:
# Valor que asigna la importancia del sentimiento en el clustering final.
# Va de 0 a infinito (en infinito, el puntaje de los usuarios tiene importancia 0).
importance = 2.5

reduced_data["reviewSentiments"] = reduced_data["reviewSentiments"].apply(lambda x: x * importance)

from sklearn.cluster import KMeans

km = KMeans(5)

km.fit(reduced_data[["reviewSentiments", "overall"]])

clusters = km.predict(reduced_data[["reviewSentiments", "overall"]])

plt.scatter(reduced_data["overall"], reduced_data["reviewSentiments"], c =clusters)


reduced_data["reviewSentiments"] = reduced_data["reviewSentiments"].apply(lambda x: x / importance)

Este gráfico permite observar como se ordenan los cluster a partir de una importancia 2.5, esto significa una ponderación en el eje de los sentimientos (vertical) por 2.5, en donde a más importancia los cluster toman más el valor sentimental para su formación que los puntajes.

A continuación se presenta una serie de gráficos que tienen una importancia de 0, 5 y 50, donde se pude observar claramente este cambio en la distribuución de los clusters, de vertical a horizontal a medida crece este valor.

WhatsApp%20Image%202018-08-24%20at%205.10.48%20PM.jpeg

De forma de obtener otra mirada a lo anteriormente mencionado, se adjuntan box plot de la misma situación, donde se puede observar como disminuye la cantidad de outliers a medida que se aumenta la importancia, además de achicarse las cajas con bigotes.

WhatsApp%20Image%202018-08-24%20at%205.08.36%20PM.jpeg

Finalmente, se puede decir que se obtuvo una métrica, aunque esta no es continua como se esperaba al inicio del proyecto, sin embargo funciona como buen indicador.

Es importante destacar que es difícil analizar los resultados porque no se puede comparar a mano el análisis sentimental con los cluster que quedan finalmente debido a la gran cantidad de datos, de esta forma no se puede comprobar si es objetiva la medida o no, dependiendo finalmente del valor de importancia que se le otrogan a los sentimientos.

Conclusiones finales

De nuestro análisis se puede concluir en primera instancia que los reviews están naturalmente sesgados a valores más altos, la gran mayoría estando sobre las 3.5 estrellas. Esto causa problemas al intentar usar los reviews de productos en amazon como medidas de decisión sobre la compra de un producto. Junto a este sesgo de los reviews, podemos notar además una tendencia a valores enteros en los promedios de los reviews en ciertos productos. Esto lleva a la medida a ser poco expresiva. Dado que los reviews sólo utilizan un valor entero para puntuar al producto, entonces en productos con pocos reviews, es más probable que se obtengan reviews con el mismo puntaje, y posiblemente menos representativos de la calidad real del producto. Además se puede ver un claro potencial en el desarrollo de una nueva medida de calidad del producto, basada en un factor ignorado en la evaluación mostrada en amazon. Este factor es el texto incluído junto a los reviews. Nuestra hipótesis es que se puede crear una medida que nos diga el sentimiento de una persona con respecto a un producto, utilizando técnicas de minería de datos, para que se pueda utilizar en conjunto al valor en estrellas del review y posiblemente normalizado con respecto al número de reviews para evitar sesgos hacia puntajes más altos, y falta de varianza en los reviews. Con esta información se comprueban la hipótesis sobre los datos planteadas anteriormente, pero un poco refinadas, sabiendo que los conjuntos donde se observa mayor disparidad son aquellos donde el producto tiene 1, 2 o 4 estrellas. Además, se pueden encontrar los productos que tienen un puntaje en uno de estos grupos sesgados, y pueden ser refinados usando el sentimiento del usuario para colocarlos en una clase más adecuada. Esto se pudo observar gracias al análisis sentimental aplicado a los datos, lo que permitió observar cierta discrepancia entre la nota que otorga el usuario con lo que escribe en el review.

Finalmente, se concluye que efectivamente se puede implementar una nueva medida que para el caso de este proyecto, corresponde a la agrupación por cluster tomando en cuenta los sentimientos en los reviews. Cabe destacar que se debe tener en cuenta que la importancia de los sentimientos no puede ser muy grande comparado a la puntuación otorgada por los usuarios a los distintos productos, ya que si este fuera el caso, se pueden generar resultados completamente distorsionados y escapan de lo esperado. Además, esta medida es discreta, al ser implementada mediante clustering, existiendo la posibilidad de aplicar una medida continua mediante regresor o clasificador, pero para aplicar una de estas las puntuaciones finales no serían del todo objetivas al estar nuestro pensamiento involucrado en esta, generando una contradicción con las hipótesis planteadas a lo largo del semestre, es por esto que las medidas continuas no se consideraron como solución.

Agradecimientos

[0] Hutto, C.J. & Gilbert, E.E. (2014). VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text. Eighth International Conference on Weblogs and Social Media (ICWSM-14). Ann Arbor, MI, June 2014.