Grafón: ¿Qué electivas me conviene cursar?
Contents
Grafón: ¿Qué electivas me conviene cursar?#
Este grafo se encarga de analizar “gente que haya tenido experiencias facultativas similares”, sin importar en qué año o cuatrimestre fue.
La idea es que si me fue parecido en varias materias a alguna otra persona, y esa persona cursó alguna electiva que yo no, entonces esa materia es una buena candidata para mí.
Por ejemplo: por más que Juan se haya recibido el año pasado, es una persona que se parece mucho a mí porque nos sacamos las mismas notas en muchas materias. Probablemente las electivas que Juan eligió, me sirvan a mí como guía.
¿Cómo es el grafo?#
El grafo analizado va a ser un grafo simple, y no un multigrafo: entre cada par de alumnos solo puede haber una arista. Manejamos la cardinalidad en el peso, en vez de tener múltiples aristas.
Nodos: alumnos
Aristas: conectar dos alumnos que hayan cursado la misma materia y “les fue parecido”.
A los dos nos fue muy bien (nos sacamos entre 8 y 10)
A los dos nos fue más o menos (nos sacamos un 6 o un 7)
A los dos nos fue mal (nos sacamos un 4 o un 5)
Peso: La inversa* de la cantidad de materias en donde somos similares
* Calculamos la inversa porque mientras más similares son, menor peso queremos que haya entre la arista, para que más cercanos estén
import pandas as pd
import utils
df = pd.read_pickle('fiuba-map-data.pickle')
df = df.dropna(axis=1, how='all')
display(df.sample(3))
df.shape
Padron | Carrera | Orientacion | Final de Carrera | materia_id | materia_nota | materia_cuatrimestre | optativas | aplazos | |
---|---|---|---|---|---|---|---|---|---|
65236 | 999876 | informatica | Sistemas Distribuidos | tesis | 66.02 | 8.0 | 2020.0 | NaN | NaN |
14118 | 0000 | informatica | Gestión Industrial de Sistemas | tpp | 75.07 | 6.0 | 2015.0 | NaN | NaN |
14982 | 108111 | informatica | Sistemas Distribuidos | NaN | 75.43 | -2.0 | 2024.0 | NaN | NaN |
(21787, 9)
# Armamos las categorias de que les haya hido parecido a dos alumnos
categories = {
4: 0,
5: 0,
6: 1,
7: 1,
8: 2,
9: 2,
10: 2
}
# Sacamos materias en final y a cursar
df_alumnos = df[df['materia_nota'] >= 4]
# Sacamos gente que no le pone la nota a su fiubamap y deja que se saco (casi) todos 4s directamente
df_alumnos['mediana'] = df_alumnos.groupby('Padron')['materia_nota'].transform('median')
df_alumnos = df_alumnos[df_alumnos['mediana'] > 5]
df_alumnos['nota_categoria'] = df_alumnos['materia_nota'].apply(lambda x: categories[x])
# Juntamos el grafo con si mismo para tener la similiritud entre cada par de padrones
df_simil_sinagg = utils.construir_df_pareando_padrones_por(df_alumnos, 'nota_categoria')
df_simil_sinagg = df_simil_sinagg[['materia_id', 'nota_categoria', 'Padron_x', 'materia_nota_x', 'Padron_y', 'materia_nota_y']]
# Unificar aristas ( con el objetivo de no tener pocos nodos y millones de aristas )
df_simil = df_simil_sinagg.groupby(['Padron_x', 'Padron_y']).agg(cant_materias_similares=('materia_id', 'count'))
df_simil = df_simil.reset_index()
df_simil['inv_cant_materias_similares'] = df_simil['cant_materias_similares'].max() - df_simil['cant_materias_similares'] + 1
df_simil[['Padron_x', 'Padron_y', 'cant_materias_similares', 'inv_cant_materias_similares']]
# Nota: Al no tener en cuenta el factor temporal en este grafo (en que cuatri cursó cada alumno), esta vez no se aplica el filtro de "sacar todos los alumnos que no setean el cuatrimestre"
# Ese filtro es considerable, por lo que tiene sentido que este grafo sea bastante mas grande que grafazo
display(df_simil.sample(3))
df_simil.shape
Padron_x | Padron_y | cant_materias_similares | inv_cant_materias_similares | |
---|---|---|---|---|
130951 | 105250 | datatouilleesdelrojo | 4 | 32 |
88093 | 103879 | 102340 | 3 | 33 |
311716 | 95512 | 99393 | 4 | 32 |
(355938, 4)
# Veamos los padrones más parecidos entre sí
df_simil.sort_values('cant_materias_similares', ascending=False).head(10)
Padron_x | Padron_y | cant_materias_similares | inv_cant_materias_similares | |
---|---|---|---|---|
13117 | 100687 | 99732 | 35 | 1 |
344280 | 99732 | 100687 | 35 | 1 |
341282 | 99616 | 99732 | 32 | 4 |
344849 | 99732 | 99616 | 32 | 4 |
40248 | 102145 | 100687 | 31 | 5 |
12594 | 100687 | 102145 | 31 | 5 |
43722 | 102192 | 102145 | 30 | 6 |
40300 | 102145 | 102192 | 30 | 6 |
11950 | 100680 | 101483 | 30 | 6 |
26079 | 101483 | 100680 | 30 | 6 |
import networkx as nx
import matplotlib.pyplot as plt
G = nx.from_pandas_edgelist(df_simil,
source='Padron_x',
target='Padron_y',
edge_attr='inv_cant_materias_similares',
create_using=nx.Graph())
utils.stats(G)
utils.plot(G, edge_width=0.0005)
Graph with 642 nodes and 177969 edges
El diámetro de la red: 3
El grado promedio de la red: 554.42
Puentes globales: []

Comunidades#
Este análisis de comunidades que vamos a realizar no está basado en una hipótesis previa de homofilia o similitud entre los nodos, como lo visto en grafazo. En lugar de ello, el objetivo del análisis es explorar los patrones de conexión que existen en el grafo y agrupar los nodos en comunidades para tener un mejor entendimiento de los mismos.
Este enfoque exploratorio es útil cuando se tiene un conjunto de datos desconocido o no se cuenta con una hipótesis clara sobre los patrones de relación entre los nodos los (como se tenía en grafazo con respecto a las camadas). El análisis de comunidades puede ayudarnos a identificar grupos de nodos que presentan patrones similares de conexión, lo que puede llevar a la identificación de relaciones interesantes entre los nodos y a la formulación de nuevas hipótesis sobre el grafo.
A partir del modelado de grafon, vamos a poder identificar comunidades de alumnos que han tenido un desempeño similar en sus materias y que, por lo tanto, podrían compartir intereses académicos. Es por eso que tomaremos esto como base para poder identificar qué cursaron las personas similares a mi con el fin de otorgar un listado de recomendaciones.
from networkx.algorithms import community
louvain = community.louvain_communities(G, weight='inv_cant_materias_similares')
utils.plot_communities(G, louvain, edge_width=0.0005)

from config import PADRON, CARRERA
from utils import plan_estudios
def nestedsearch(el, lst_of_sets):
return list(filter(lambda lst: el in lst, lst_of_sets))[0]
def materias_padron(padron):
return df[(df['Padron'] == padron) & (df['materia_nota'] >= 4)]['materia_id'].values
def sugerir_electivas(padron):
min_alumnos, max_alumnos = 6, 20
max_iteraciones = 25
i = 0
grupo = []
for i in range(max_iteraciones):
louvain = community.louvain_communities(G, weight='inv_cant_materias_similares', resolution=1+(i*0.01))
comunidad = nestedsearch(padron, louvain)
if min_alumnos <= len(comunidad) <= max_alumnos:
grupo = comunidad
break
elif not grupo or (len(comunidad) >= max_alumnos and (len(comunidad) - max_alumnos <= len(grupo) - max_alumnos)):
grupo = comunidad
i+=1
df_sugerencias = df_alumnos[df_alumnos['Padron'].isin(grupo)].groupby('materia_id').agg(cant_alumnos_similares=('materia_id', 'count'))
df_sugerencias = df_sugerencias[~df_sugerencias.index.isin(materias_padron(padron))]
df_materias = pd.read_json(plan_estudios(CARRERA))
df_sugerencias = pd.merge(df_sugerencias, df_materias, left_on='materia_id', right_on="id")
df_sugerencias = df_sugerencias[df_sugerencias['categoria'] == 'Materias Electivas']
df_sugerencias = df_sugerencias[['id', 'materia', 'creditos', 'cant_alumnos_similares']].sort_values('cant_alumnos_similares', ascending=False)
return df_sugerencias.reset_index(drop=True)
print(f"Top 10 electivas sugeridas a {PADRON}")
electivas = sugerir_electivas(PADRON)
electivas.head(10)
Top 10 electivas sugeridas a 100029
id | materia | creditos | cant_alumnos_similares | |
---|---|---|---|---|
0 | 78.xx | Idioma | 4 | 12 |
1 | 61.10 | Análisis Matemático III A | 6 | 7 |
2 | 62.15 | Física III D | 4 | 6 |
3 | 71.18 | Estructura Económica Argentina | 4 | 4 |
4 | 66.20 | Organización de Computadoras | 6 | 3 |
5 | 71.12 | Estructura de las Organizaciones | 6 | 3 |
6 | 66.06 | Análisis de Circuitos | 10 | 2 |
7 | 75.30 | Teoría de Algoritmos II | 6 | 2 |
8 | 64.05 | Estática y Resistencia de Materiales B | 6 | 1 |
9 | 66.69 | Criptografía y Seguridad Informática | 6 | 1 |