Alumno: Trosman, Denis
Fecha de entrega: 02/03/2023
Profesor: Álvaro Bravo
import pymongo
from pymongo import MongoClient
import json
from json import loads
from bson import json_util
import pandas as pd
import seaborn as sns
sns.set(rc = {'figure.figsize':(15,8)})
import matplotlib.pyplot as plt
import plotly.express as px
myclient = MongoClient('localhost',27017) #Cambiar por el propio si no conecta.
Para cargar el dataset se ha utilizado Mongo DB Compass. Luego de haberlo cargado allí y haber conectado el cliente en Jupyter, podemos cargar la conexión con los siguientes comandos.
mydb = myclient["test"]
mydb.Tech.drop() #Eliminamos por si ya existe la colección
tech = mydb['Tech'] #Creamos colección vacia
with open('C:/Users/denis/OneDrive/Escritorio/Master/Modulo 9 - NoSQL/Tarea/tech.json', errors='ignore',encoding='utf8') as f:
data = json.load(f,encoding="utf8")
carga = tech.insert_many(data)
len(carga.inserted_ids)
3000
El dataset fue obtenido desde la plataforma kaggle y contiene información de empleos ofrecidos por empresas startups, como tambien data sobre estas mismas.
El dataset cuenta con 3000 registros.
Las variables del dataset son:
Fuente: https://www.kaggle.com/datasets/chickooo/top-tech-startups-hiring-2023
Observamos dataset en formato df de python para ver la dimensionalidad y nulos
df = pd.DataFrame(list(tech.find()))
df.head(3)
| _id | id | company_name | headline | tags | website | employees | about | locations | industries | jobs | logo_url | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 63ebaa90f780d1df97632e8a | 1 | Forward Networks | Network automation software for a stronger, mo... | [Actively Hiring, Highly rated, Growing fast, ... | http://www.forwardnetworks.com/ | 11-50 | The software used to manage networks hasn’t ke... | [Palo Alto] | [Enterprise Software, Information Technology, ... | {'Engineering': 7, 'Founder': 2, 'Investor': 1... | NaN |
| 1 | 63ebaa90f780d1df97632e8b | 2 | Wise | We're on a mission to bring transparency to fi... | [Actively Hiring, Highly rated, Recently funde... | https://wise.com | 1001-5000 | Current banking systems don’t let us send, spe... | [New York City, Singapore, London, Tampa, Buda... | [Financial Services, Consumers, Payments, Peer... | {'Designer': 13, 'Engineering': 103, 'Founder'... | NaN |
| 2 | 63ebaa90f780d1df97632e8c | 3 | Benchling | Informatics platform to accelerate, measure, a... | [Actively Hiring, Highly rated, Growing fast, ... | http://www.benchling.com | 201-500 | Benchling makes life science research faster a... | [Boston, San Francisco] | [Biotechnology, Life Sciences, Enterprise Soft... | {'Designer': 1, 'Engineering': 13, 'Founder': ... | NaN |
El dataframe cuenta con 3000 filas y 12 columnas.
df.shape
(3000, 12)
Contamos con un gran numero de nulos en la columna logo_url, por lo que esta sera eliminada
df.isnull().sum()
_id 0 id 0 company_name 0 headline 15 tags 0 website 0 employees 0 about 0 locations 0 industries 0 jobs 0 logo_url 2998 dtype: int64
tech.update_many({},{"$unset":{"logo_url":""}})
pd.DataFrame(list(tech.find())).head(3)
| _id | id | company_name | headline | tags | website | employees | about | locations | industries | jobs | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 63ebaa90f780d1df97632e8a | 1 | Forward Networks | Network automation software for a stronger, mo... | [Actively Hiring, Highly rated, Growing fast, ... | http://www.forwardnetworks.com/ | 11-50 | The software used to manage networks hasn’t ke... | [Palo Alto] | [Enterprise Software, Information Technology, ... | {'Engineering': 7, 'Founder': 2, 'Investor': 1... |
| 1 | 63ebaa90f780d1df97632e8b | 2 | Wise | We're on a mission to bring transparency to fi... | [Actively Hiring, Highly rated, Recently funde... | https://wise.com | 1001-5000 | Current banking systems don’t let us send, spe... | [New York City, Singapore, London, Tampa, Buda... | [Financial Services, Consumers, Payments, Peer... | {'Designer': 13, 'Engineering': 103, 'Founder'... |
| 2 | 63ebaa90f780d1df97632e8c | 3 | Benchling | Informatics platform to accelerate, measure, a... | [Actively Hiring, Highly rated, Growing fast, ... | http://www.benchling.com | 201-500 | Benchling makes life science research faster a... | [Boston, San Francisco] | [Biotechnology, Life Sciences, Enterprise Soft... | {'Designer': 1, 'Engineering': 13, 'Founder': ... |
El campo Jobs esta con formato Objeto. Lo convertimos a Array para poder obtener la cantidad total de trabajos abiertos, sin importar el rubro.
#Primero agregamos el nuevo array
tech.update_many({},
[{"$addFields":
{ 'job_array':
{ '$objectToArray': "$jobs" }}}])
#Luego creamos un campo que sea la suma total de trabajos
tech.update_many({},
[{"$addFields":
{ 'totalJobs':
{ '$sum': "$job_array.v" }}}])
#Observamos muestra para confirmar funcionamiento
show = pd.DataFrame(list(tech.find()))
show[show.columns[-3:]].head(3)
| jobs | job_array | totalJobs | |
|---|---|---|---|
| 0 | {'Engineering': 7, 'Founder': 2, 'Investor': 1... | [{'k': 'Engineering', 'v': 7}, {'k': 'Founder'... | 31 |
| 1 | {'Designer': 13, 'Engineering': 103, 'Founder'... | [{'k': 'Designer', 'v': 13}, {'k': 'Engineerin... | 504 |
| 2 | {'Designer': 1, 'Engineering': 13, 'Founder': ... | [{'k': 'Designer', 'v': 1}, {'k': 'Engineering... | 51 |
Notamos que Ninja Van es la empresa que mas empleados busca adquirir, seguida de Bluelight Consulting.
#Sort
query = { "totalJobs": -1 }
ordeno = { '$sort': query }
#Seleccion de columnas
project = { "$project": { "_id": 0, 'company_name':1,'totalJobs':1,'website':1} }
etapas = [ordeno, project]
#Aplicación
show = pd.DataFrame(tech.aggregate(pipeline=etapas))
show.head(10).style.background_gradient(cmap='Blues').hide_index()#.applymap(lambda x: f"color: {'black' if isinstance(x,str) else 'grey'}")
| company_name | website | totalJobs |
|---|---|---|
| Ninja Van | https://www.ninjavan.co/ | 1041 |
| Bluelight Consulting | https://bluelight.co | 817 |
| VillageMD | http://www.villagemd.com/ | 507 |
| Wise | https://wise.com | 504 |
| Bolt | http://www.bolt.eu | 496 |
| Red Ventures | http://www.redventures.com | 392 |
| Red Ventures | http://www.redventures.com | 392 |
| Box | http://www.box.com | 391 |
| Cruise | http://www.getcruise.com | 368 |
| Hopper | http://www.hopper.com | 358 |
sns.set(rc = {'figure.figsize':(15,8)})
data= show.head(10)
ax = sns.barplot(data=data,x='company_name',y='totalJobs', palette = 'Blues_r')
ax.set_title('Ranking de empresas con más puestos disponibles',fontsize=20)
ax.set_facecolor('white')
plt.show()
#Agrupacion
query = { "_id": "$employees",
"promedio": { '$avg': "$totalJobs" },
'cantidad': {'$sum': 1} }
agrupo = { '$group': query }
#Sort
query2 = { "promedio": -1 }
ordeno = { '$sort': query2 }
#Cambio nombre de id
project = { "$project": { "_id": 0, "employees": "$_id", 'promedio':1,'cantidad':1} }
etapas = [ agrupo, ordeno, project]
#Aplicación
show = pd.DataFrame(tech.aggregate(pipeline=etapas))
show.style.hide_index().applymap(lambda x: f"color: {'red' if isinstance(x,str) else 'black'}")
| promedio | cantidad | employees |
|---|---|---|
| 105.444444 | 72 | 1001-5000 |
| 84.222222 | 9 | 5000+ |
| 49.500000 | 106 | 501-1000 |
| 25.725490 | 255 | 201-500 |
| 12.500000 | 752 | 51-200 |
| 6.267640 | 1233 | 11-50 |
| 5.411867 | 573 | 1-10 |
Aunque el número de empresas según su cantidad de empleados en el dataset este desbalanceada (gráfico derecha), podemos apreciar con un promedio como las empresas mas grandes son aquellas que tienen más posiciones libres, algo que tiene sentido.
Sin embargo, algo interesante es como empresas de 1000-5000 empleados tienen mayor cantidad de posiciones libres que las de +5000. Esto puede deberse a que siguen en crecimiento, mientras que las otras han llegado a su pico.
sns.set(rc = {'figure.figsize':(15,8)})
fig, axes = plt.subplots(1,2)
order = ['1-10','11-50','51-200','201-500','501-1000','1001-5000','5000+']
ax =sns.barplot(data= show,x='employees',y='promedio', palette='Blues_r', ax = axes[0],order=order)
ax.set_title('Cantidad promedio de nuevas posiciones según empleados actuales',fontsize=15)
ax.set_facecolor('white')
ax2 = sns.barplot(data= show,x='employees',y='cantidad', palette='magma', ax = axes[1],order=order)
ax2.set_title('Cantidad de empresas en la base según empleados actuales',fontsize=15)
ax2.set_facecolor('white')
plt.show()
Veamos ahora la distribución en % de la cantidad de empleados en la base de datos
#Agrupamiento 1
fase1 = { '$group': {"_id": "$employees", 'qty': { '$sum': "$totalJobs" }} }
docu = { 'employees': "$_id", 'qty': "$qty" }
#Agrupamiento 2 para calcular total
fase2 = { '$group': { '_id': 'null', 'tot': {'$sum': "$qty" }, 'items': { '$push': docu } } }
#Unwind para recuperar los items
fase3 = {'$unwind': "$items" }
merge = [ { 'tot': "$tot" ,'qty': "$qty", 'employees': "$employees"}, "$items" ]
fase4 = {'$replaceWith': { '$mergeObjects': merge } }
#Calculo de porcentaje
fase5 = { '$addFields': { "%employees": { "$multiply":[{"$divide":["$qty","$tot"]}, 100]} } }
#Aplicación
pipeline = [ fase1 , fase2, fase3, fase4, fase5]
show = pd.DataFrame(tech.aggregate(pipeline=pipeline))
#Plots
sns.set(rc = {'figure.figsize':(15,8)})
order = ['1-10','11-50','51-200','201-500','501-1000','1001-5000','5000+']
ax =sns.barplot(data= show,x='employees',y='%employees', palette='Blues_r',order=order)
ax.set_title('Distribución muestral de cantidad de empleados en start-ups',fontsize=15)
ax.set_facecolor('white')
Para poder visualizar la cantidad de puestos disponibles según su naturaleza, fue necesario realizar un unwind al array de trabajos para luego poder agrupar y calcular la suma de estos.
desagregacion = { '$unwind': "$job_array" }
#Group by
grupo = { "_id": "$job_array.k", "cantidad": { '$sum': "$job_array.v" }}
agrupo = { '$group': grupo }
#Sort
query_ordeno = { "cantidad": -1 }
ordeno ={ '$sort': query_ordeno }
#Visualizacion
project = { "$project": { "_id": 0, "type": "$_id", "cantidad":1} }
#Agregacion
etapas = [desagregacion, agrupo, ordeno,project]
show = pd.DataFrame(tech.aggregate(pipeline=etapas))
#Plot
fig = px.bar(show, x="type", y="cantidad",text_auto='.2s')
fig.update_layout(title="Cantidad de puestos disponibles según tipo de puesto")
fig.update_layout({
'plot_bgcolor': 'rgba(0, 0, 0, 0)',
'paper_bgcolor': 'rgba(0, 0, 0, 0)'})
fig.update_traces(marker_color='rgb(158,202,225)', marker_line_color='rgb(8,48,107)',
marker_line_width=1.5, opacity=0.6)
fig.show()
Se puede notar que el puesto mas buscado por las startups es el de ingenieros (13K), seguido de investors (8K) y ventas (6.5K).
Similar al ejercicio anterior, ahora vemos la cantidad de puestos disponibles según industria principal de las start ups.
Dado que el campo industria viene como un array, se obtuvo con la funcion $first el primer elemento de este, considerandolo como principal.
Luego se agrupó y ordeno para presentar un grafico del top 10.
#Sustraemos el primer elemento y lo consideramos industria principal
addfields = {'$addFields': { 'industria_principal': { '$first': "$industries" } } }
#Agrupamos
grupo = { "_id": "$industria_principal", "Puestos": { '$sum': "$totalJobs" }}
agrupo = { '$group': grupo }
#Sort
query_ordeno = { "Puestos": 1 }
ordeno ={ '$sort': query_ordeno }
#Visualizacion
project = { "$project": { "_id": 0, "Industria": "$_id", "Puestos":1} }
#Agregacion
etapas = [addfields, agrupo, ordeno, project]
show = pd.DataFrame(tech.aggregate(pipeline=etapas)).tail(10)
show
#Plot
fig = px.bar(show, x="Puestos", y="Industria",text_auto='.2s', orientation='h')
fig.update_layout(title="Cantidad de puestos disponibles según industria")
fig.update_layout({
'plot_bgcolor': 'rgba(0, 0, 0, 0)',
'paper_bgcolor': 'rgba(0, 0, 0, 0)'})
fig.update_traces(marker_color='rgb(158,202,225)', marker_line_color='rgb(8,48,107)',
marker_line_width=1.5, opacity=0.6)
fig.show()
Como se puede notar en el gráfico, la mayor cantidad de puestos disponibles son en startups cuya industria principal es Software as a Service (SaaS) (5.7K). A este le siguen los puestos para empresas especializadas en Enterprise Software (3.6K) y Mobile (3.5K).
Siguiendo la lógica anterior, diremos que la primer ciudad del campo locations es la principal y la que tomaremos para el ranking de puestos disponibles.
Las ciudades principales con mayor cantidad de puestos disponibles son NYC y San Francisco.
#Sustraemos el primer elemento y lo consideramos industria principal
addfields = {'$addFields': { 'Ciudad': { '$first': "$locations" } } }
#Agrupamos
grupo = { "_id": "$Ciudad", "Puestos": { '$sum': "$totalJobs" }}
agrupo = { '$group': grupo }
#Sort
query_ordeno = { "Puestos": 1 }
ordeno ={ '$sort': query_ordeno }
#Visualizacion
project = { "$project": { "_id": 0, "Ciudad": "$_id", "Puestos":1} }
#Agregacion
etapas = [addfields, agrupo, ordeno, project]
show = pd.DataFrame(tech.aggregate(pipeline=etapas)).tail(10)
show
#Plot
fig = px.bar(show, x="Puestos", y="Ciudad",text_auto='.2s', orientation='h')
fig.update_layout(title="Cantidad de puestos disponibles según ciudad")
fig.update_layout({
'plot_bgcolor': 'rgba(0, 0, 0, 0)',
'paper_bgcolor': 'rgba(0, 0, 0, 0)'})
fig.update_traces(marker_color='rgb(50,150,50)', marker_line_color='rgb(8,48,107)',
marker_line_width=1.5, opacity=0.6)
fig.show()
Para este paso, se transformara al numero de empleados actuales a una variable númerica, tomando el valor medio del rango. Esto se hará a través de la creación de una tabla nueva, utilizando el comando insertMany, y luego joineandolo a la base original con el comando lookup.
mydb.empleos.drop() #Eliminamos por si ya existe la colección
empleos = mydb['empleos'] #Creamos colección vacia
#Llenamos la coleccion con insert many, tomando el valor medio del rango categorico.
empleos.insert_many( [
{ 'employees': "1-10", 'numero_empleados': 5},
{ 'employees': "11-50", 'numero_empleados': 30},
{ 'employees': "51-200", 'numero_empleados': 125},
{ 'employees': "201-500", 'numero_empleados': 350},
{ 'employees': "501-1000", 'numero_empleados': 750},
{ 'employees': "1001-5000", 'numero_empleados': 3000},
{ 'employees': "5000+", 'numero_empleados': 5000},
] )
#Hacemos Join con colección principal
join = {
'$lookup':
{
'from': 'empleos',
'localField': 'employees',
'foreignField': 'employees',
'as': 'numero_empleados'
}
}
#Obtenemos parte del array que nos interesa y calculamos el Ratio
unwinder = {'$unwind': '$numero_empleados' }
merge = [ { 'employees': "$employees", 'numero_empleados': "$numero_empleados.numero_empleados", 'totalJobs':'$totalJobs'}]
merger = {'$replaceWith': { '$mergeObjects': merge } }
addfields = { '$addFields': { "employees_puestos_ratio": { "$multiply":[{"$divide":["$totalJobs","$numero_empleados"]}, 1]} } }
#Agrupamos
grupo = { "_id": "$employees", "Ratio": { '$avg': "$employees_puestos_ratio" }}
agrupo = { '$group': grupo }
#Visualizacion
project = { "$project": { "_id": 0, "Empleados": "$_id", "Ratio":1} }
#Aplicamos
etapas = [join,unwinder,merger,addfields,agrupo,project]
show = pd.DataFrame(tech.aggregate(etapas))
show
#Plots
sns.set(rc = {'figure.figsize':(15,8)})
order = ['1-10','11-50','51-200','201-500','501-1000','1001-5000','5000+']
ax =sns.barplot(data= show,x='Empleados',y='Ratio', palette='Blues_r',order=order)
ax.set_title('Ratio puestos disponibles / número de empleados, según número de empleados',fontsize=15)
ax.set_facecolor('white')
Como tiene sentido, el ratio es mayor para aquellos con pocos empleados, ya que pueden estar en epocas de expansión
Eliminamos aquellos que tengan 0 posiciones abiertas o más que 500. 5 documentos son eliminados.
#Creamos query filtro
query = { '$or':[
{ "totalJobs": 0},
{ "totalJobs": { '$gt':500 }}
]
}
#Delete many
tech.delete_many(query)
#Enseñamos resultado
pd.DataFrame(tech.find()).shape
(2995, 13)
Actualizaremos a los que tengan mas de 5000 empleados como entre 1001 y 5000 para que no sea un grupo pequeño a la hora de efectuar modelos futuros
query = {'employees':'5000+'}
actualizacion = { '$set': { "employees": "1001-5000" } }
#Aplicamos
tech.update_many(query,
actualizacion)
show = pd.DataFrame(tech.find())
show['employees'].value_counts()
11-50 1232 51-200 752 1-10 572 201-500 255 501-1000 105 1001-5000 79 Name: employees, dtype: int64