Test de Hipótesis: Análisis de los 50 libros más vendidos en amazon (2009 - 2019)
Hypothesis Testing
2-Sample T-Test
Mann–Whitney U test
Welch’s T-Test
Este análisis profundiza en cómo el género literario influye en la valoración de los usuarios, el número de reseñas y el precio de los bestsellers en Amazon.
Dataset sobre los 50 libros más vendidos de Amazon de 2009 a 2019. Contiene 550 libros, los datos se han clasificado en ficción y no-ficción utilizando Goodreads.
Hipótesis a probar
Vamos a responder a las siguientes preguntas con validez estadística:
¿Los géneros difieren en “User Rating”?
¿Los géneros difieren en número de “Reviews”?
¿Los géneros difieren en términos de “Price”?
Matemáticas y código: en Inglés
Aunque el texto principal está en español, los términos matemáticos y el código están en inglés.
Esta práctica sigue el estándar internacional y ayuda a familiarizarse con el lenguaje técnico más utilizado en el campo de la ciencia de datos.
1b. Descripción de datos
Para empezar, importamos las librerías que vamos a utilizar:
Pandas: Pandas es una biblioteca esencial en la ciencia de datos que proporciona estructuras de datos flexibles y eficientes, como DataFrames, para el análisis y manipulación de datos tabulares. Es ampliamente utilizada para limpiar, transformar y analizar datos, lo que la convierte en una herramienta fundamental para la preparación de datos en proyectos de ciencia de datos.
Scipy: Scipy es una biblioteca que se construye sobre NumPy y ofrece una amplia variedad de módulos y funciones especializadas para aplicaciones científicas y matemáticas. Incluye herramientas para estadísticas, optimización, álgebra lineal y procesamiento de señales, lo que la hace esencial en la investigación y el análisis de datos en ciencia de datos.
Fuzzywuzzy: Fuzzywuzzy es una biblioteca que se utiliza en la ciencia de datos para comparar cadenas de texto difusas o parcialmente coincidentes. Es útil en la limpieza y normalización de datos de texto, así como en la identificación de similitudes entre strings, lo que es valioso en tareas como la duplicación de registros o la coincidencia de nombres en bases de datos.
Plotly Express: Plotly Express es una biblioteca de visualización de datos que simplifica la creación de gráficos interactivos y visuales. Es especialmente útil en la exploración de datos y la comunicación de resultados en ciencia de datos, permitiendo a los científicos de datos crear visualizaciones informativas y atractivas con facilidad.
Code
# Import librariesimport pandas as pdfrom scipy import statsfrom fuzzywuzzy import fuzz# Import plotly and customizeimport plotly.io as pioimport plotly.express as px# Set a global template, e.g. "plotly_dark"# Customize the color scheme of the chosen template, e.g. "Set2"pio.templates.default ="plotly"pio.templates["plotly"].layout.colorway = px.colors.qualitative.Set2
Cargamos el dataset y describimos brevemente sus características.
df = pd.read_csv("bestsellers with categories.csv")df.head(5)
Name
Author
User Rating
Reviews
Price
Year
Genre
0
10-Day Green Smoothie Cleanse
JJ Smith
4.7
17350
8
2016
Non Fiction
1
11/22/63: A Novel
Stephen King
4.6
2052
22
2011
Fiction
2
12 Rules for Life: An Antidote to Chaos
Jordan B. Peterson
4.7
18979
15
2018
Non Fiction
3
1984 (Signet Classics)
George Orwell
4.7
21424
6
2017
Fiction
4
5,000 Awesome Facts (About Everything!) (Natio...
National Geographic Kids
4.8
7665
12
2019
Non Fiction
Campo
Tipo de Dato
Descripción
Ejemplo
Name
Texto
Título del libro.
11/22/63: A Novel
Author
Texto
Autor del libro.
Stephen King
User Rating
Numérico entre 0 y 5 (con un decimal)
Calificación promedio otorgada por usuarios.
4.6
Reviews
Numérico (entero)
Cantidad de reseñas del libro.
2052
Price
Numérico (entero)
Precio de venta del libro.
22
Year
Año (número entero)
Año de inclusión en la lista de más vendidos.
2011
Genre
Categórico
Género del libro, ficción o no ficción.
Ficción
Estos son los tipos de datos que se identifican en primer momento. Como veremos más adelante, estas designaciones pueden ser problemáticas según los valores que contenga el dataset y los insights que queramos obtener.
2a. Preparación de los datos
2a.1 Typecasting
Comprobamos el tipo de datos de las columnas y los modificamos conforme nuestra descripción inicial.
df.dtypes
Name object
Author object
User Rating float64
Reviews int64
Price int64
Year int64
Genre object
dtype: object
Name category
Author category
User Rating float64
Reviews int64
Price int64
Year int64
Genre category
dtype: object
2a.2 Manejo de duplicados
Simpler approach
¿Cuantas filas son exactamente iguales?
df.duplicated().sum()
0
Es normal no encontrar duplicados en este caso, ya que hay libros que se repiten, pero es imposible que coincidan en “Year”. No es un error en sí mismo. Sin embargo, hay un problema. Las “Reviews” deberían ser las que el libro tenía en el año en el que fue uno de los más vendidos. En lugar de eso, son las del último año. Esto podría deberse a que el autor del dataset no tuvo acceso al historial de las reseñas.
Repeated Bestsellers
df[df.duplicated("Name")]
Name
Author
User Rating
Reviews
Price
Year
Genre
10
A Man Called Ove: A Novel
Fredrik Backman
4.6
23848
8
2017
Fiction
21
All the Light We Cannot See
Anthony Doerr
4.6
36348
14
2015
Fiction
33
Becoming
Michelle Obama
4.8
61133
11
2019
Non Fiction
36
Between the World and Me
Ta-Nehisi Coates
4.7
10070
13
2016
Non Fiction
41
Brown Bear, Brown Bear, What Do You See?
Bill Martin Jr.
4.9
14344
5
2019
Fiction
...
...
...
...
...
...
...
...
543
Wonder
R. J. Palacio
4.8
21625
9
2016
Fiction
544
Wonder
R. J. Palacio
4.8
21625
9
2017
Fiction
547
You Are a Badass: How to Stop Doubting Your Gr...
Jen Sincero
4.7
14331
8
2017
Non Fiction
548
You Are a Badass: How to Stop Doubting Your Gr...
Jen Sincero
4.7
14331
8
2018
Non Fiction
549
You Are a Badass: How to Stop Doubting Your Gr...
Jen Sincero
4.7
14331
8
2019
Non Fiction
199 rows × 7 columns
Hay 199 libros que han sido bestsellers durante más de un año. Es importante tener en cuenta esto para el análisis. Vamos a utilizar únicamente la muestra más reciente de cada libro.
Checking for authors or titles with different spellings
Comprobamos si hay autores o títulos iguales pero escritos diferente.
# Function to find similar author namesdef find_similar(df, column): authors = df[column].unique() duplicates = []for i, author1 inenumerate(authors):for author2 in authors[i +1 :]: ratio = fuzz.ratio(author1, author2)if ratio >90: # You can adjust this threshold according to your criteria duplicates.append((author1, author2))return duplicates
# Find similar author namesduplicates = find_similar(bestsellers, "Author")print("Author names that refer to the same author but are written differently:")for dup in duplicates:print(dup)# Find similar title namesduplicates = find_similar(bestsellers, "Name")print("Title names that refer to the same title but are written differently:")for dup in duplicates:print(dup)
Author names that refer to the same author but are written differently:
('George R. R. Martin', 'George R.R. Martin')
('J.K. Rowling', 'J. K. Rowling')
Title names that refer to the same title but are written differently:
('The 5 Love Languages: The Secret to Love That Lasts', 'The 5 Love Languages: The Secret to Love that Lasts')
('The Girl Who Played with Fire (Millennium Series)', 'The Girl Who Played with Fire (Millennium)')
# Replace the names of the Authors with the correct onesbestsellers = bestsellers.replace("George R. R. Martin", "George R.R. Martin")bestsellers = bestsellers.replace("J. K. Rowling", "J.K. Rowling")# Replace the names of the Authors with the correct onesbestsellers = bestsellers.replace("The 5 Love Languages: The Secret to Love That Lasts","The 5 Love Languages: The Secret to Love that Lasts",)bestsellers = bestsellers.replace("The Girl Who Played with Fire (Millennium Series)","The Girl Who Played with Fire (Millennium)",)
bestsellers
Name
Author
User Rating
Reviews
Price
Year
Genre
0
10-Day Green Smoothie Cleanse
JJ Smith
4.7
17350
8
2016
Non Fiction
1
11/22/63: A Novel
Stephen King
4.6
2052
22
2011
Fiction
2
12 Rules for Life: An Antidote to Chaos
Jordan B. Peterson
4.7
18979
15
2018
Non Fiction
3
1984 (Signet Classics)
George Orwell
4.7
21424
6
2017
Fiction
4
5,000 Awesome Facts (About Everything!) (Natio...
National Geographic Kids
4.8
7665
12
2019
Non Fiction
...
...
...
...
...
...
...
...
538
Winter of the World: Book Two of the Century T...
Ken Follett
4.5
10760
15
2012
Fiction
539
Women Food and God: An Unexpected Path to Almo...
Geneen Roth
4.2
1302
11
2010
Non Fiction
544
Wonder
R. J. Palacio
4.8
21625
9
2017
Fiction
545
Wrecking Ball (Diary of a Wimpy Kid Book 14)
Jeff Kinney
4.9
9413
8
2019
Fiction
549
You Are a Badass: How to Stop Doubting Your Gr...
Jen Sincero
4.7
14331
8
2019
Non Fiction
351 rows × 7 columns
Ideas destacadas
Hemos corregido las variaciones ortográficas de los títulos y autores.
Hemos seleccionado las muestras más recientes de cada libro.
Con esto, se ha reducido el tamaño del dataset de 550 a 351 muestras.
2a.3 Análisis de valores atípicos
selected_columns = ["Reviews", "Price"]for column in selected_columns: fig = px.box(df, x=column, title=f"Boxplot of {column}") fig.update_layout(title_x=0.5) fig.show()
Ideas destacadas
Se pueden hacer tres cosas con los outliers, siguiendo la mnemotecnia 3R: rectificar, retener o remover. En este caso, podemos estar ante valores atípicos genuinos, por tanto vamos a retenerlos por el momento.
2a.4 Variables con varianza cercana a cero
Si la variance es cero, la variable no aporta información para el análisis estadístico o el modelado, por lo tanto, puede ser eliminada.
Si la variance es casi cero, también es una variable candidata a ser eliminada, ya que aporta poca información y puede generar ruido en el análisis.
Es importante tener en cuenta que el umbral para considerar una variable como “Near Zero Variance” puede variar según el contexto y el problema específico que se esté abordando.
selected_columns = ["User Rating", "Reviews", "Price"]# Calculate the variance of each columnvariances = bestsellers[selected_columns].var()# Define a threshold for variancethreshold =0.1# Identify columns with near-zero or very small variancezero_variance_cols = variances[variances <= threshold].indexprint("Columns with Zero & Near Zero Variance:")print(zero_variance_cols)
Columns with Zero & Near Zero Variance:
Index(['User Rating'], dtype='object')
La variable “User Rating” parece estar por debajo del umbral que hemos escogido arbitrariamente. Vamos a representarla gráficamente para comprender mejor la situación.
Al trabajar con un dataset de bestsellers, la mayoría de los “User Rating” están entre 4 y 5 estrellas, mientras que inicialmente se esperaba que las muestras estuvieran repartidas entre 0 y 5.
Es importante tener esto presente en posteriores análisis, ya que casi podría considerarse una variable categórica en lugar de continua.
2a.5 Valores ausentes o faltantes
bestsellers.isnull().sum()
Name 0
Author 0
User Rating 0
Reviews 0
Price 0
Year 0
Genre 0
dtype: int64
Ideas destacadas
No hay missing values
4. Pruebas de hipótesis
Vamos a responder a las siguientes preguntas con validez estadística:
Parece que existen diferencias. ¿Pero son estadísticamente significativas? Vamos a comprobarlo con un test de hipótesis.
1. Seleccionamos la hipótesis y el nivel de significancia
H0: El “User Rating” de los libros de ficción es igual que el de los libros de no ficción.
Ha: El “User Rating” de los libros de ficción es diferente al de los libros de no ficción.
alpha = 0.05
2. Identificamos el tipo de test
Deseamos comparar las medias de dos grupos de nuestra sample. Por lo tanto, un 2-Sample t-Test parece ser adecuado. Lo primero es verificar si se cumplen los requisitos del test.
2.1 Requisitos del test
Tenemos una sample representativa de la population.
Los datos son continuos.
Las muestras siguen una distribución normal o hay más de 15 observaciones.
Los grupos son independientes.
Las varianzas son iguales (o al menos similares).
Examinemos nuestra sample para ver si podemos aplicar el test.
Libro de refencia
Book: Hypothesis Testing An Intuitive Guide For Making Data Driven Decisions
Page: 48
Section: 2-Sample t-Tests
Número de observaciones para cada grupo:
bestsellers["Genre"].value_counts()
Genre
Non Fiction 191
Fiction 160
Name: count, dtype: int64
def shapiro_test(data, alpha=0.05):""" Perform the Shapiro-Wilk test to check the normality of the data. Parameters: data (array-like): The data to analyze. alpha (float): Significance level. Returns: str: The test result. """ statistic, p_value = stats.shapiro(data)if p_value < alpha:return"We reject the null hypothesis. The data does not follow a normal distribution."else:return"We fail to reject the null hypothesis. The data can be considered normally distributed."result_fiction = shapiro_test(fiction_bestsellers["User Rating"])result_nonfiction = shapiro_test(nonfiction_bestsellers["User Rating"])print("For fiction bestsellers:", result_fiction)print("For non-fiction bestsellers:", result_nonfiction)
For fiction bestsellers: We reject the null hypothesis. The data does not follow a normal distribution.
For non-fiction bestsellers: We reject the null hypothesis. The data does not follow a normal distribution.
Estudiemos la relación entre las varianzas de cada grupo.
# Perform the Levene's Teststatistic, p_value = stats.levene( fiction_bestsellers["User Rating"], nonfiction_bestsellers["User Rating"])# Significance levelalpha =0.05# Check for significanceif p_value < alpha:print("We reject the null hypothesis. The variances are not similar.")else:print("We fail to reject the null hypothesis. The variances are similar.")
We reject the null hypothesis. The variances are not similar.
Volvamos sobre los requisitos del test:
¿Tenemos una sample representativa de la population? Suponemos que sí.
¿Los datos son continuos?
En este contexto, los “User Rating” que van de 0 a 5 con un único decimal pueden considerarse como datos continuos. Sin embargo, observando las gráficas, vemos que la mayoría de los valores se encuentran en un intervalo muy pequeño (mayores a 4.0). Esto dificulta considerarlos como continuos.
¿Las muestras siguen una distribución normal o hay más de 15 observaciones?
No, las muestras no siguen una distribución normal. Pero hay más de 15 observaciones en cada grupo, gracias al teorema central del límite podemos renunciar al supuesto de normalidad.
¿Los grupos son independientes? Sí.
¿Las varianzas son iguales (o al menos similares)?
No.
Dada la falta de continuidad en los datos, vamos a realizar un test no paramétrico Mann-Whitney.
Book:Hypothesis Testing An Intuitive Guide For Making Data Driven Decisions
Page: 341
Section: Analyzing Likert Scale Data
# Perform the Mann-Whitney U teststatistic, p_value = stats.mannwhitneyu( nonfiction_bestsellers["User Rating"], fiction_bestsellers["User Rating"])# Print the resultsprint("p-value:", p_value)# Check for significancealpha =0.05# Significance levelif p_value < alpha:print("We reject the null hypothesis. There are significant differences between the groups." )else:print("We fail to reject the null hypothesis. There are no significant differences between the groups." )
p-value: 0.019226481868505015
We reject the null hypothesis. There are significant differences between the groups.
Ideas destacadas
Podemos afirmar que hay diferencias significativas en términos de “User Rating” entre los libros de ficción y los de no ficción.
Parece que existen difencias. ¿Pero son estadisticamente significativas? Vamos a comprobarlo con un test de hipótesis.
1. Seleccionamos la hipotesis y el nivel de significancia
H0: El número de ‘Reviews’ medio de los libros de ficción es igual que el de los libros de no ficción
Ha: El número de ‘Reviews’ medio de los libros de ficción es diferente que el de los libros de no ficción
alpha = 0.05
2. Identificamos el tipo de test
Deseamos comparar las medias de dos grupos de nuestra sample. Por lo tanto, un 2-Sample t-Test parece ser adecuado. Lo primero es verificar si se cumplen los requisitos del test.
2.1 Requisitos del test
Tenemos una sample representativa de la population.
Los datos son continuos.
Las muestras siguen una distribución normal o hay más de 15 observaciones.
Los grupos son independientes.
Las varianzas son iguales (o al menos similares).
Examinemos nuestra sample para ver si podemos aplicar el test.
Libro de refencia
Book: Hypothesis Testing An Intuitive Guide For Making Data Driven Decisions
Page: 48
Section: 2-Sample t-Tests
Número de observaciones para cada grupo:
bestsellers["Genre"].value_counts()
Genre
Non Fiction 191
Fiction 160
Name: count, dtype: int64
Realizamos un test de normalidad para cada grupo:
result_fiction = shapiro_test(fiction_bestsellers["Reviews"])result_nonfiction = shapiro_test(nonfiction_bestsellers["Reviews"])print("Para bestsellers de ficción:", result_fiction)print("Para bestsellers de no ficción:", result_nonfiction)
Para bestsellers de ficción: We reject the null hypothesis. The data does not follow a normal distribution.
Para bestsellers de no ficción: We reject the null hypothesis. The data does not follow a normal distribution.
Estudiemos la relación entre las varianzas de cada grupo.
# Perform the Levene's Teststatistic, p_value = stats.levene( fiction_bestsellers["User Rating"], nonfiction_bestsellers["User Rating"])# Significance levelalpha =0.05# Check for significanceif p_value < alpha:print("We reject the null hypothesis. The variances are not similar.")else:print("We fail to reject the null hypothesis. The variances are similar.")
We reject the null hypothesis. The variances are not similar.
Volvamos sobre los requisitos del test:
¿Tenemos una sample representativa de la population? Suponemos que sí.
¿Los datos son continuos? Sí.
¿Las muestras siguen una distribución normal o hay más de 15 observaciones?
No, las muestras no siguen una distribución normal. Pero hay más de 15 observaciones en cada grupo, gracias al teorema central del límite podemos renunciar al supuesto de normalidad.
¿Los grupos son independientes? Sí.
¿Las varianzas son iguales (o al menos similares)?
No, las varianzas no son similares.
Las varianzas no son similares. Vamos a realizar un test tipo Welch’s t-test.
Para realizar el test utilizaremos scipy.stats.ttest_ind. Es necesario definir el parámetro equal_varbool como False.
# Perform the Welch's t-test (equal_var=False)statistic, p_value = stats.ttest_ind( nonfiction_bestsellers["Reviews"], fiction_bestsellers["Reviews"], equal_var=False)# Print the resultsprint("p-value:", p_value)# Check for significancealpha =0.05# Significance levelif p_value < alpha:print("We reject the null hypothesis: There are significant differences between the groups." )else:print("We cannot reject the null hypothesis: There are no significant differences between the groups." )
p-value: 4.3970747273288255e-07
We reject the null hypothesis: There are significant differences between the groups.
Ideas destacadas
Podemos afirmar que hay diferencias significativas en términos de número de “Reviews” entre los libros de ficción y los de no ficción. Los libros del género de ficción obtienen, en media, más Reviews que los libros de no ficción.
En esta ocasión “Price” podría no variar en función del género. ¿Pero es estadísticamente significativo? Vamos a comprobarlo con un test de hipótesis.
1. Seleccionamos la hipotesis y el nivel de significancia
H0: El precio medio de los libros de ficción es igual que el de los libros de no ficción
Ha: El precio medio de los libros de ficción es diferente que el de los libros de no ficción
alpha = 0.05
2. Identificamos el tipo de test
Deseamos comparar las medias de dos grupos de nuestra sample. Por lo tanto, un 2-Sample t-Test parece ser adecuado. Lo primero es verificar si se cumplen los requisitos del test.
2.1 Requisitos del test
Tenemos una sample representativa de la population.
Los datos son continuos.
Las muestras siguen una distribución normal o hay más de 15 observaciones.
Los grupos son independientes.
Las varianzas son iguales (o al menos similares).
Examinemos nuestra sample para ver si podemos aplicar el test.
Libro de refencia
Book: Hypothesis Testing An Intuitive Guide For Making Data Driven Decisions
Page: 48
Section: 2-Sample t-Tests
Realizamos un test de normalidad para cada grupo:
result_fiction = shapiro_test(fiction_bestsellers["Price"])result_nonfiction = shapiro_test(nonfiction_bestsellers["Price"])print("Para bestsellers de ficción:", result_fiction)print("Para bestsellers de no ficción:", result_nonfiction)
Para bestsellers de ficción: We reject the null hypothesis. The data does not follow a normal distribution.
Para bestsellers de no ficción: We reject the null hypothesis. The data does not follow a normal distribution.
Estudiemos la relación entre las varianzas de cada grupo.
# Perform the Levene's Teststatistic, p_value = stats.levene( fiction_bestsellers["Price"], nonfiction_bestsellers["Price"])# Significance levelalpha =0.05# Check for significanceif p_value < alpha:print("We reject the null hypothesis. The variances are not similar.")else:print("We fail to reject the null hypothesis. The variances are similar.")
We fail to reject the null hypothesis. The variances are similar.
Volvamos sobre los requisitos del test:
¿Tenemos una sample representativa de la population? Suponemos que sí.
¿Los datos son continuos? Sí.
¿Las muestras siguen una distribución normal o hay más de 15 observaciones?
No, las muestras no siguen una distribución normal. Pero hay más de 15 observaciones en cada grupo, gracias al teorema central del límite podemos renunciar al supuesto de normalidad.
¿Los grupos son independientes? Sí.
¿Las varianzas son iguales (o al menos similares)? Sí.
Se cumplen los requisitos para realizar un 2-Sample t-Test.
# Perform the t-teststatistic, p_value = stats.ttest_ind( nonfiction_bestsellers["Price"], fiction_bestsellers["Price"])# Print the resultsprint("p-value:", p_value)# Check for significancealpha =0.05# Significance levelif p_value < alpha:print("We reject the null hypothesis: There are significant differences between the groups." )else:print("We cannot reject the null hypothesis: There are no significant differences between the groups." )
p-value: 0.1398175523683507
We cannot reject the null hypothesis: There are no significant differences between the groups.
Ideas destacadas
No encontramos diferencias en el precio en función del género del libro.
5. Recapitulación y reflexiones
Durante la preparación de los datos:
Hemos realizado una limpieza en el dataset, reduciendo el número de muestras para el estudio de 549 a 331.
Hemos descubierto que user rating concentra sus muestras en 10 valores en lugar de los 50 esperados.
Durante el estudio mediante test de hipótesis:
Hemos mostrado con significancia estadística que el género del libro influye tanto en el número de reviews como en el user rating.
Hemos mostrado con significancia estadística que el género del libro no influye en el precio del mismo.