# Debido a que existen algunas incompatibilidades, instalaremos la libreria:
!pip install openpyxl==3.0.10
# Se hacen las importaciones necesarias y se renombran por comodidad. Notese que se importa tambien
# openpyxl para que el archivo en excel no presente problemas.
# imports
import numpy as np
import pandas as pd
import sqlite3 as sql3
import openpyxl
import matplotlib.pyplot as plt
import seaborn as sns
import math
# Es necesario recuperar los datos de las diferentes fuentes.
# Como los articulos, los vendedores y las ordenes proceden de distintos origenes, se utilizan metodos
# diferentes:
# Artículos: Se crea una conexion a la base de datos virtuales que nos provee sqlite3 en el archivo de
# base de datos "articles.db". Luego, se hace la consulta. Cómo queremos traer todos los registros,
# hacemos un Select * from... Los guardamos en una nueva variable que seria el resultado de la consulta.
# Por ultimo, con pandas tomamos esa variable y la convertimos en un DataFrame por medio de la funcion
# que nos provee pandas.
conn = sql3.connect('/work/data/articles.db')
sql_query = pd.read_sql_query('SELECT * FROM articles', conn)
df_articles = pd.DataFrame(sql_query, columns = ['article_id', 'article_name', 'unit_price'])
# Vendedores: En este caso, simplemente usamos la funcion que nos provee pandas para traer los datos de
# un archivo de excel. Solamente tenemos que indicarle en donde se encuentra el indice y obtenemos el
# DataFrame buscado.
df_sellers = pd.read_excel('/work/data/sellers.xlsx', index_col=0)
# Órdenes: Por ultimo el caso mas sencillo: Para leer las ordenes de un archivo con valores separados
# por coma, usamos la funcion que nos provee pandas y ya. Con eso se arma el DataFrame.
df_orders = pd.read_csv('/work/data/orders.csv')
# Exploración del df de artículos
print('Muestra de datos: Se seleccionan sólo 5 valores como referencia.')
print(df_articles.head())
print('\nFormato del dataframe: Se brinda la información de "(filas, columnas)".')
print(df_articles.shape)
print('\nBúsqueda de valores nulos por columna: Si existiera algun valor, nos mostraria la cantidad')
print('mayor a 0')
print(df_articles.isnull().sum())
print('\nFormato de los datos por columna: Se ve el tipo de dato, en donde se puede notar que por lo ')
print('menos article_id es esperado. Los otros se devuelven con el tipo object, mientras que se ')
print('se espera que article_name sea una cadena de caracteres y unit_price sea un numero con coma.')
print('El valor del precio unitario tiene que ser de tipo float, por lo cual deberá convertirse luego.')
print(df_articles.dtypes)
# Exploración del df de vendedores
print('Muestra de datos: Se seleccionan solo 5 valores como referencia.')
print(df_sellers.head())
print('\nFormato del dataframe: Se brinda la información de "(filas, columnas)".')
print(df_sellers.shape)
print('\nBúsqueda de valores nulos por columna: Si existiera algun valor, nos mostraria la cantidad')
print(df_sellers.isnull().sum())
print('\nFormato de los datos por columna: Se ve el tipo de dato, en donde se puede notar que se ')
print('devuelven las columnas con el tipo object, mientras que se espera que seller_name sea una ')
print('cadena de caracteres.')
print(df_sellers.dtypes)
# Exploración del df de órdenes
print('Muestra de datos: Se seleccionan solo 5 valores como referencia.')
print(df_orders.head())
print('\nFormato del dataframe: Se brinda la información de "(filas, columnas)".')
print(df_orders.shape)
print('\nBúsqueda de valores nulos por columna: Si existiera algun, nos mostraria la cantidad')
print(df_orders.isnull().sum())
print('\nFormato de los datos por columna: Se ve el tipo de dato, en este caso todos coinciden con ')
print('el tipo esperado menos country_name que se supone sea una cadena de caracteres.')
print(df_orders.dtypes)
#La conversion de tipo object a float se puede hacer con el metodo .astype(float).
df_articles['unit_price'] = df_articles['unit_price'].astype(float)
print('\nLa estructura del DataFrame corregido seria la siguiente:')
print(df_articles.dtypes)
# Se creará una copia del df_orders para utilizarlo de base en donde se introduciran los demas cambios.
# Estos cambios modelaran el DataFrame buscado (my_df).
my_df = df_orders.copy()
# Luego, es necesario hacer el cambio del índice del df_articles para utilizarlo luego.
df_articles.set_index('article_id',inplace = True)
# Se agregaran las columnas necesarias asociadas al campo que va a utilizarme como referencia.
my_df = my_df.assign(article_name = my_df['article_id'])
my_df = my_df.assign(total_amount = my_df['article_id'])
my_df = my_df.assign(seller_name = my_df['seller_id'])
# Ahora, se reemplazaran los valores en el nuevo DataFrame
# Primero se debe buscar el nombre del artículo y se guarda en una variable.
# Como ahora df_articles está indexado por article_id, se puede utilizar para conseguir
# el registro que buscado mas facilmente con ".loc".
# La sintaxis correcta sería: df_articles.loc[indice][columna]
# En donde [indice] será el dato que se obtiene de my_df.loc[i]['article_id']
# En otras palabras, se toma registro a registro el article_id y se utiliza para extraer
# el nombre del artículo. Para lograr esto se utiliza la funcion iterativa 'for'
for i in range(len(my_df.index)): #len(my_df.index) devuelve la cantidad de registros
idArticulo = my_df.loc[i]['article_id']
nombreArticulo = df_articles.loc[idArticulo]['article_name']
# Se le asigna a la columna correspondiente, llamada ahora como la variable nombreArticulo
my_df.loc[i,'article_name'] = nombreArticulo
# Se hace la misma operacion con precioArticulo y cantArticulo para obtener el paralelo
# de total_amount. En este caso será el resultado del producto de ambos.
precioArticulo = df_articles.loc[idArticulo]['unit_price']
cantArticulo = my_df.loc[i]['quantity']
my_df.loc[i,'total_amount'] = precioArticulo * cantArticulo
# La iteracion tambien debe obtener datos de la columna de seller_name, ahora llamada nombreVendedor
idVendedor = my_df.loc[i]['seller_id']
nombreVendedor = df_sellers.loc[idVendedor]['seller_name']
my_df.loc[i,'seller_name'] = nombreVendedor
# Para finalizar, se puede limpiar la tabla eliminando las columnas que no se necesitan, como pueden
# ser order_id, article_id y/o seller_id. Para esto se utiliza el metodo del Dataframe .drop.
my_df.drop(['order_id', 'article_id', 'seller_id'], axis = 'columns', inplace=True)
print(my_df)
# Por otro lado, también es bueno saber que existe una funcion de pandas llamada .merge
# Con esta funcion se pueden unir dos DataFrames para obtener un unico DataFrame de resultado.
# El resultado es similar a hacer un 'join' en la base de datos con dos tablas.
# Por ejemplo, siendo d1:
d1=pd.DataFrame({'mes': ['ene','feb','mar','abr'], 'ventas':[10,20,30,15]})
print('\nDataFrame d1:')
print (d1)
# Y siendo d2:
d2=pd.DataFrame({'mes': ['ene','feb','mar','abr'], 'costos':[7,16,25,12]})
print('\nDataFrame d2:')
print (d2)
# El df resultado será:
print('\nDataFrame luego de aplicar merge sobre d1 y d2:')
print(pd.merge(d1,d2))
# Siendo 'mes': ['ene','feb','mar','abr'] la 'key' que tienen en común.
# Para mas informacion: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.merge.html
# RESOLUCIÓN ANALÍTICA: Se crea un nuevo DataFrame en donde primero se agrupara por article_name y luego
# se sumaran las cantidades de cada articulo.
my_df2=my_df.groupby('article_name').sum()
por_cant = my_df2.sort_values('quantity', ascending=False) #ascending=True traeria los menos vendidos.
print(por_cant['quantity'].head(1)) #variando el valor de .head(x) podriamos traer los x mas vendidos.
# RESOLUCIÓN GRÁFICA: Se utilizará seaborn para realizar el plot.
sns.barplot(y=por_cant["quantity"], x=por_cant.index)
plt.xticks(rotation=75) #Si no se le da una rotacion, las nombres se superponen haciendose ilegibles.
plt.show() # Muestra la grafica.
print('\nSe observa que el articulo "HDD" tuvo mas unidades vendidas.')
# RESOLUCIÓN ANALÍTICA: Se crea un nuevo DataFrame en donde primero se agrupara por article_name y luego
# se ordenaran segun el monto final de forma descendente. Elegimos los primeros 10.
df2 = (my_df.groupby(by='article_name').sum()).sort_values('total_amount',ascending=False).head(10)
print(df2['total_amount'])
# RESOLUCIÓN GRÁFICA: Se opto por tomar sólo los 10 primeros artículos que más ingresos proporcionaron
# Pero no puede hacerse una comparativa ni porcentajes porque no se trabaja con el total de los datos.
print('\nTop 10 articulos que mas ingresos generaron vistos en gráfica de torta: ')
plt.pie(x=df2['total_amount'], labels=df2.index)
plt.show()
print('\nTop 10 articulos que mas ingresos generaron vistos en gráfica barras horizontales: ')
plt.barh(df2.index,df2['total_amount'] , height=0.7) # height permite hacer las barras mas finas y
plt.show() # asi resultan mas fáciles de distinguir.
print('\nEn ambos casos se ve claramente que el articulo mas vendido fue "Full Pc".')
# RESOLUCIÓN ANALÍTICA: Se crea otro nuevo DataFrame como copia del original, esta vez agrupado por
# seller_name y se hará la suma de los montos totales. Luego, se ordenará el DataFrame de tal forma que
# los mejores vendedores queden arriba y siga de forma descendente.
df4 = (my_df.groupby(by='seller_name').sum()).sort_values('total_amount',ascending=False)
print(df4[['quantity']+['total_amount']])
# RESOLUCIÓN GRÁFICA: Se elige un grafico de barras verticales y se procede a graficar.
plt.bar(df4.index,df4['total_amount'])
plt.xticks(rotation=60, ha="right") # ha="right" corrige el orden de los nombres para que coincidan con
plt.show() # las columnas.
print('\nSe observa que el vendedor Janel O Curran vendió un monto total superior a otros vendedores.')
# RESOLUCIÓN ANALÍTICA: Se agrupan los datos segun la semana, se suman y se acomodan de forma
# descendente. De esa manera se obtienen los valores totales por semana.
df5 = (my_df.groupby(by='week').sum()).sort_values('total_amount',ascending=False)
print(df5['total_amount'])
# RESOLUCIÓN GRÁFICA
plt.bar(df5.index,df5['total_amount'])
plt.show()
plt.pie(x=df5['total_amount'], labels=df5.index)
plt.show()
print('Se observa que la semana que mas se vendió fue la numero 1.')
# RESOLUCIÓN ANALITICA:
# ¿Cuales son los 5 paises que menos compraron en el mes?
print('\nTop 5 paises que menos compraron:')
df6 = (my_df.groupby(by='country_name').sum()).sort_values('total_amount',ascending=True).head()
plt.bar(df6.index,df6['total_amount'])
plt.show()
print('\nEn el grafico se ve claramente que, de los ultimos 5, Puerto Rico fue el pais que menos compró')
# RESOLUCIÓN ANALITICA:
# Previsualizar el DF final
# -------------------------
# "Pais" "Semana" "Ventas"
# Pais1 4 monto
# Pais2 3 monto
# ----------------------
# Paisn x monto
# 1) Se obtiene el total por semana por pais
df_pais_semana = my_df.groupby(['country_name', 'week']).sum()
#print(df_pais_semana) nos ayuda a visualizar lo obtenido
# 2) Se obtiene la lista de paises
paises = my_df.groupby('country_name').sum().index.values
# 3) Se genera el DataFrame final
df_final = pd.DataFrame(columns=['Semana', 'Ventas'], index=paises)
#print(df_final)
# 4) Se procesa el DataFrame df_pais_semana y se obtienen las ventas minimas por semana.
# Se guardan en df_final.
for p in paises:
df = df_pais_semana.loc[p]
v = df.min()['total_amount']
s = df[(df['total_amount'] == v)].index[0]
df_final.loc[p] = {'Semana': s, 'Ventas': v}
df_final['Semana'] = df_final['Semana'].astype(int)
print(df_final)
# RESOLUCION GRÁFICA:
g = sns.barplot(data = df_final, x = df_final.index, y = 'Ventas', hue = 'Semana')
g.set_xticklabels(labels=df_final.index, rotation=90)
print('\nEn el siguiente grafico de seaborn se utilizan colores para distinguir las semanas, y el alto de la columna')
print('ilustraria el monto vendido. Si bien responde a la pregunta, hay valores que se pierden por su bajo monto.')
plt.show()
plt.bar(df_final.index,df_final['Semana'])
plt.xticks(rotation=90)
print('\nEn este caso, se grafica la misma respuesta independientemente de los montos semanales.')
print('Responde de manera mas clara a la pregunta.')
plt.show()
# RESOLUCIÓN ANALITICA
df_mejores = (my_df.groupby(by='seller_name').sum()).sort_values('total_amount',ascending=False).head(3)
print('\nDataFrame mejores vendedores')
print(df_mejores)
df_peores = (my_df.groupby(by='seller_name').sum()).sort_values('total_amount',ascending=True).head(3)
print('\nDataFrame peores vendedores')
print(df_peores)
plt.bar(df_mejores.index,df_mejores['total_amount'])
plt.xticks(rotation=90)
plt.bar(df_peores.index,df_peores['total_amount'])
plt.xticks(rotation=90)
print('\nMejores vendedores vs Peores vendedores')
plt.show()
print('\nTotal vendido entre mejores vendedores y peores vendedores:')
total_mejores = df_mejores.sum().total_amount
total_peores = df_peores.sum().total_amount
plt.bar('Mejores vendedores',total_mejores)
plt.bar('Peores vendedores',total_peores)
plt.bar('Diferencia',total_mejores-total_peores)
plt.show()
porcentual = math.trunc(((total_mejores/total_peores)-1)*100)
print('\nTeniendo en cuenta los datos, se estima una mejora de alrededor de ',porcentual,'% en las ventas de los ')
print('peores vendedores trabajando en conjunto con el mejor equipo')