Trabajo Bases de Datos NO SQL¶

ucm.JPG

Alumno: Trosman, Denis

Fecha de entrega: 02/03/2023

Profesor: Álvaro Bravo

Importación de librerías y conexión con localhost¶

In [1]:
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

Creamos cliente con localhost por default¶

In [2]:
myclient = MongoClient('localhost',27017) #Cambiar por el propio si no conecta.

Asignamos database y colección a utilizar¶

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.

In [3]:
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)
Out[3]:
3000

Dataset¶

El dataset fue obtenido desde la plataforma kaggle y contiene información de empleos ofrecidos por empresas startups, como tambien data sobre estas mismas.

start.png

El dataset cuenta con 3000 registros.

Las variables del dataset son:

  • _id: (ObjectId)
  • company_name: id (integer)
  • headline: Descripcion de empleos (string)
  • tags: Publicidad de empleos (array of string)
  • website: Pagina web (string)
  • employees: Cantidad de empleos de la companía (range of integers) (string)
  • about: Descripción de la companía (string)
  • locations: Locación de la companía (array of string)
  • industries: Industrias en las que se especializa la companía (array of string)
  • jobs: Cantidad de trabajos por tipo (key-value pair)
  • logo_url: Logo (NaN)

Fuente: https://www.kaggle.com/datasets/chickooo/top-tech-startups-hiring-2023

Análisis del dataset¶

Observamos dataset en formato df de python para ver la dimensionalidad y nulos

In [4]:
df = pd.DataFrame(list(tech.find()))
df.head(3)
Out[4]:
_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.

In [5]:
df.shape
Out[5]:
(3000, 12)

Contamos con un gran numero de nulos en la columna logo_url, por lo que esta sera eliminada

In [6]:
df.isnull().sum()
Out[6]:
_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

Eliminamos field logo_url¶

In [7]:
tech.update_many({},{"$unset":{"logo_url":""}})
pd.DataFrame(list(tech.find())).head(3)
Out[7]:
_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': ...

Conversion de fields¶

El campo Jobs esta con formato Objeto. Lo convertimos a Array para poder obtener la cantidad total de trabajos abiertos, sin importar el rubro.

In [8]:
#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)
Out[8]:
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

Top 10 empresas con mayores puestos disponibles¶

Notamos que Ninja Van es la empresa que mas empleados busca adquirir, seguida de Bluelight Consulting.

In [9]:
#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'}")
Out[9]:
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
In [11]:
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()

Trabajos segun cantidad de empleados de la empresa¶

In [12]:
#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'}")
Out[12]:
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.

In [13]:
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

In [14]:
#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')

Categorías de trabajo disponibles¶

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.

In [15]:
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).

meme.jpg

Industrias¶

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.

In [16]:
#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).

meme_saas.jpeg

Ciudades¶

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.

In [17]:
#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()

Número de puestos disponibles según número de empleados actuales.¶

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.

In [18]:
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

Eliminación de outliers¶

Eliminamos aquellos que tengan 0 posiciones abiertas o más que 500. 5 documentos son eliminados.

In [19]:
#Creamos query filtro

query = { '$or':[
        { "totalJobs": 0},
        { "totalJobs": { '$gt':500 }}
        ]
    }

#Delete many
tech.delete_many(query)

#Enseñamos resultado
pd.DataFrame(tech.find()).shape
Out[19]:
(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

In [20]:
query = {'employees':'5000+'}

actualizacion = { '$set': { "employees": "1001-5000" } }

#Aplicamos
tech.update_many(query,
                actualizacion)

show = pd.DataFrame(tech.find())

show['employees'].value_counts()
Out[20]:
11-50        1232
51-200        752
1-10          572
201-500       255
501-1000      105
1001-5000      79
Name: employees, dtype: int64

Conclusiones¶

  • Hemos cargado desde Pymongo una colección a nuestra base de datos creada en Compass.
  • El fichero, con información sobre empresas Start-ups y sus puestos abiertos, fue analizado y ajustado para generar insights.
  • Observamos como la mayor cantidad de puestos son ofrecidos por start-ups con 1001-5000 empleados actuales, y en ciudades como New York y San Francisco.
  • Asi mismo, los puestos son del rubro Software as a Service, y buscando ingenieros.
  • Como práctica, se han eliminado outliers sobre la variable puestos abiertos, y agrupado categorías para mejorar los resultados de una posible modelación futura.