Skip to content

This is a freelance project I made for a tech consultancy in Barcelona. I was asked to implement a network-graph data visualization tool for an internal corporate application (project description in Catalan)

Notifications You must be signed in to change notification settings

blackcub3s/networkGraph_dataVisualization

Repository files navigation

PREÀMBUL

Aclariment

Aquest programa fou un treball freelance per una companyia del sector tecnològic de Barcelona, per tal d'obtenir una eina de representació de dades en forma de graf no dirigit. Aquí hi ha el codi que vaig compartir amb la companyia, però anonimitzat tant en el fitxer de dades original, noms de variables i comentaris dels diferents fitxers .py que conté (per questions relacionades amb un NDA).

En l'adaptació que he fet s'ha buscat una forma de reaprofitar el treball que vaig fer de tal manera que es mantingui l'ànima original del projecte, en el seu aspecte tècnic; però que mostri unes dades diferents, igualment d'encaixables en l'estructura del programa que vaig fer.

Teconologies utilitzades

Per fer aquest projecte s'ha fet servir Python, com a llenguatge de programació i diverses llibreries disponibles per a aquest llenguatge: plotly, per fer la representació gràfica del graf; Networkx, per tal de representar internament el graf com un conjunt de nodes i d'arestes i passar-los certes propietats. També s'ha fet servir JSON per tal de guardar les coordenades de cada node per successives crides de la funció que crea el gràfic.

DESCRIPCIÓ

Finalitat del programa, explicació de variables de l'excel i del gràfic

Podeu obrir l'excel 1. fitxerInversions_inicial.xlsx per veure les dades introduides. S'ha escollit fer un sistema per visualitzar inversors i les companyies en les quals aquests inverteixen, tot mostrant en quin moment aquestes inversions es dupliquen en valor (tenen un ROI del 100%).

Per tal de fer aquesta representació s'ha introduit com a nodes en un graf tant diversos inversors de renom (columna Investor) com algunes companyies en les quals aquests inversors pressumiblement han invertit (columna Investment). Una aresta entre un inversor (Investor) i una inversió (Investment) implica que va haver una operació de compra d'accions per part de l'inversor en aquella companyia. Si us situeu damunt l'aresta que uneix ambdós, veureu més dades de la inversió que va fer l'inversor donat, concretament:

  • Buy Date: La data de compra de l'acció per part de l'inversor.
  • Forecast [Investment x2] o bé [Investment x2]: Si surt la primera etiqueta ens indica la data en la qual en el futur s'estima avui en dia que es dobli una inversió que un inversor ja va fer (el dia de la Buy Date) però que encara no s'ha duplicat; si, pel contrari, surt la segona etiqueta significaria que la inversió ja es va duplicar en valor després de la seva compra i que, per tant, s'ha pogut obtenir quan ha passat això de dades d'accions que ja són registrades en el passat: sigui quin sigui el cas, la data en que la inversió es duplica o es duplicarà potencialment, apareix en la columna de l'excel anomenada Forecast_Sell_Date -que és d'on es volquen les dades de l'excel per a aquesta etiqueta de l'aresta-. Si la Forecast_Sell_Datefos posterior al moment en que vaig registrar les dades (el dia 23/02/2023) queda recollit com un 1 dins la columna Is_Forecast.

Noteu que tant les columnes Buy_Date com Forecast_Sell_Date mostren anys i que són derivades de dates més exactes (columnes PRECISE_Buy_Date i PRECISE_Forecast_Sell_Date) que hipotèticament contenen, de forma respectiva, els moments en que es varen comprar i que es podrien haver venut (o vendre en el futur) al doble del valor pel qual es van comprar. Aquestes dues columnes més precises en termes de moment temporal (no en correspondència amb la realitat!) s'utilitzen per computar la diferència en anys entre el moment de compra i el moment hipotètic de duplicació de la inversió, càlcul que es fa a la columna Time_Difference de l'excel. Aquesta columna es fa servir per calcular anys, mesos i dies que passen des de la PRECISE_Buy_Datefins a la PRECISE_Forecast_Sell_Date i mostrar-ho de forma precisa dins de cada aresta (fent servir la funció converteixAny_a_AnyMesDia()).

La generació del fitxer de dades 1. fitxerInversions_inicial.xlsx s'ha obtingut de forma iterativa fent preguntes a chatgpt (GPT3.5). No són dades reals, però es poden aproximar a la realitat, ja que ChatGPT intenta produir dades reals a partir de la base de coneixement disponible a internet fins a l'any 2021. Així doncs, preneu aquest dataset pel que és: una prova de concepte per mostrar coneixement de la llibreria NetworkX, plotly, pandas, etc. i també d'algorismia i coneixement del llenguatge Python, no com un fotografia exacta de la història de les inversions fetes per Elon Musk, Carl Icahn, David Einhorn, George Soros, John Paulston, etc.

ON FER CANVIS EN LES DADES?

Si heu fet canvis a 1. fitxerInversions_inicial heu d'executar parseExcel.pyque produirà un segon excel amb les dades depurades (excel que no heu de tocar: 2. fitxerInversions_parsejatFinal.xlsx). És d'aquest excel d'on es prenen les dades per a executar el programa principal que produeix els gràfics __main__grafGuay.py. Més endavant us explico com fer servir el teu propi dataset.

EXECUTAR EL PROGRAMA

Fer filtres

Un cop tingueu l'excel llest 1. fitxerInversions_inicial i tingueu 2. fitxerInversions_parsejatFinal.xlsx generats (que al repositori ja ho estan) heu d'executar __main__grafGuay.py.

En executar el programa __main__grafGuay.py veureu que hi ha 5 maneres possibles d'utilitzar-lo. Cada una d'aquestes maneres és un filtre que genera visualment un graf d'un subconjunt de dades escollit. Cada un d'aquests filtres es pot aplicar executant el fragment de codi adient anant dins de "if __name__ == "__main__":

Noteu que tots els fragments de codi que apliquen filtres estan comentats, menys el fragment de codi que no té filtre i que mostra totes les files que hi ha dins l'excel representades al graf (1. sense filtre). Per accedir a un dels 4 filtres que hi ha (de punts 2.1 a 2.4 de la llista següent) comenta tots els altres i deixa només com a codi llegible per l'intèrpret de python al filtre en questió escollit:

  1. sense filtre
  2. amb filtre
    • 2.1 per intervalForecast_Sell_Date
    • 2.2 per Investor
    • 2.3 per Time_Difference
    • 2.4 per timelapse de Time_Difference

Per exemple, si vols aplicar filtres per Investor hauràs d'assegurar-te que tots els altres (inclòs "1.sense filtre") estan comentats i no s'executen. Us explico cada cas particular a continuació:

1. Sense filtre

Si executeu el programa __main__grafGuay.py sense tocar res més veureu codi mostrarà el graf amb tots els nodes que provenen de l'excel. Això és perquè està aplicada la següent linia descomentada:

#EXEMPLE SENSE FILTRE (els paràmetres després de fesFiltre poden valdre qualsevol valor, no s'usen; però han d'estar inicialitzats a alguna cosa)
crea_grafic(fesFiltre = False, 
            tipusFiltre="",
            informacioFiltre=[])  

En aplicar aquesta crida a la funció crea_grafic(), si el paràmetre fesFiltre = False aleshores no s'aplica cap filtre al graf que obtindrem desde les dades de l'excel. Ens mostrarà totes les dades que hi ha a l'excel en el graf que es representa obert en una pantalla del navegador, que amb les dades que tenim, serà similar a això (el layout canvia a cada crida de la funció, perquè en la funció que el genera intervé l'atzar):

imatgeGrafSenseFiltre

El Graf és interactiu i podem veure propietats dels nodes: és a dir, el nom dels inversors i de les empreses on inverteixen. En les arestes que uneixen inversors i inversions també trobem més informació: sobre l'any de compra, l'any potencial de venta per duplicar la inversió i, finalment, el temps -exacte- que passa entre un moment i l'altre. Podeu veure-ho en el següent .gif:

gifGrafSenseFiltre

2. Amb filtre

Tenim 4 filtres possibles, que anirem desgranant en els apartats 2.1 a 2.4, respectivament. Per a aplicar-los hem de deixar el paràmetre booleà fesFiltre en True i anar a definir la resta de paràmetres tipusFiltrei informacioFiltre.1

Com hem dit, per a executar qualsevol dels quatre filtres hem d'assegurar-nos que el codi corresponent a cada filtre queda descomentat dins de if __name__ == "__main__": i que tota la resta de filtres estan comentats (veure fitxer __main__grafGuay.py).

2.1 filtre per intervalForecast_Sell_Date

El que fa aquest filtre és poder generar un subconjunt d'aquelles inversions que s'han duplicat (i els inversors que les han fet) dins del període temporal absolut definit pels anys enters mostrats en el límit inferior i superior de l'interval (que són definits a informacioFiltre, com una llista de dos elements). Les inversions i inversors que no compleixen aquest requisit surten difuminats. El codi de per cridar al filtre entre els anys 2015 i 2020 és sel següent:

#EXEMPLE AMB FILTRE PER Forecast_Sell_Date (mirar un interval d'anys i veure quins inversors han duplicat el valor de les seves inversions)
crea_grafic(fesFiltre = True,
            tipusFiltre="intervalForecast_Sell_Date", 
            informacioFiltre=[2015,2020])  #la llista que passem a informacioFiltre conté limit inferior i limit superior, respectivament. Si vols trobar inversors que podrien duplicar la seva inversió en un sol any en concret, fes que conicideixin els limits.

I el resultat del filtre anterior és:

imatgeFiltrePerForecastSellDate

2.2 filtre per Investor

Aquí teniu un exemple per filtrar per inversors (Investor). Permet filtrar les inversions d'un o diversos inversors. si volem filtrar les inversions que han fet "Elon Musk", "John Paulson" i "George Soros" els passarem com a llista al paràmetre informacioFiltre:

crea_grafic(fesFiltre = True,
            tipusFiltre="Investor", 
            informacioFiltre=["Elon Musk", "John Paulson", "George Soros"]) #Si tipusFiltre == ["George Soros"], només veuràs les inversions de Soros.

I el resultat d'aquesta crida a la funció crea_grafic(), amb els paràmetres aquí especificats, serà aquesta:

filtre_perInversor

2.3. filtre per Time_Difference

Aquest sistema de filtratge permet mostrar solsament aquells inversors i inversions que s'han duplicat en un període temporal de temps relatiu encapçulat entre un límit inferior i limit superior temporal. El filtre mirarà la diferència entre PRECISE_Forecast_Sell_Date i Precise_Buy_Date (que es la variable Time_Differencede l'excel) i filtrarà d'acord amb els límits de l'interval donats. Per definir un filtre com a límit inferior el 0 i com a superior l'1, passarem a informacioFiltre una llista amb dos paràmetres que contenen aquests límits. En l'exemple de codi següent podrem veure com representar les inversions el valor de les quals han tardat entre 0 i 3 anys a duplicar-se, i com representar els inversors que les han dut a terme, independentment de l'any en que es va produir (o espensa que es produirà) la duplicació del seu valor:

#EXEMPLE AMB FILTRE PER Time_Difference (Permet filtrar pel temps que els inversors tarden en duplicar les seves inversions)
crea_grafic(fesFiltre = True,
            tipusFiltre="Time_Difference",   
            informacioFiltre=[0,3])  #Admet dos arguments: anys d'inici i any de final (deixar en enter millor).

I el resultat del codi anterior és:

filtreperTimeDifference

2.4. filtre per timelapse de Time_difference

Aquest filtre és un filtre especial. El que fa és generar fotogrames que permeten generar un timelapse que ajuda a l'analista de dades a fer-se una imatge mental de quant de temps necessiten els inversors per duplicar les seves inversions. En els moments en que s'iluminin més quantitats de nodes per fotograma (cada fotograma és un filtre que s'amplia un any per la dreta) voldrà dir que serà el moment en que més inversions s'hagin duplicat (o s'estima que es dupliquin).

Concretament el que fa aquest filtre és cridar a un altre filtre de forma iterativa, el filtre Time_Difference, ja mostrat a l'apartat anterior (2.3). A cada iteració de l'altre filtre el que fa és aumentar un any el límit superior del paràmetre informacioFiltre del filtre per Time_Difference i, a diferència de l'anterior, guarda cada representació del graf resultant dins la carpeta timelapse_Time_Difference com a imatges .png en comptes d'obrir-lo al navegador.

En executar el filtre filtres.timelapse_Time_Difference(li = 1, ls = 20), tal i com hem deixat implícid, es crida internament la funció crea_grafic() diversos cops. Si no fem res, amb cada crida a la funció crea_grafic()el layout del graf canviaria cada vegada que s'invoca, cosa que seria un efecte indesitjat perquè volem que a cada fotograma els nodes ocupin sempre la mateixa posició en el pla. La solució fou guardar les coordenades en la primera crida a la funció, tot guardant-les en un JSON, i reutilitzar-les per a les següents.

Igual que la resta de filtres, amb aquest filtre es pot generar el timelapse assegurant-se que l'únic codi actiu dins if __name__ == "__main__" sigui aquest:

filtres.timelapse_Time_Difference(li = 0, ls = 12)

En executar-lo fareu una imatge per al primer any (0 a 1), per al primer i al segon (0 a 2), per al primer i fins al tercer (0 a 3), ..., fins a arribar a contenir tots els anys de l'interval (0 a 12), en aquest cas un total de 12 anys coberts (no cal fer intervals més grans per què el màxim període per duplicar una inversió, al menys en el dataset que he pujat jo, per als inversors famosos especificats, són poc més d'11 anys. Si definim uns intervals correctes trobarem que a l'últim fotograma tots els nodes del graf estan iluminats.

Per exemple, si trobeu que el valor més gran dins de Time_Difference és 21,34252 aleshores escolliu a ls = 22 perquè l'inclogui. Si veieu que el minim any és 0,5432 aleshores podeu posar li = 0 perquè el filtri i el mostri al graf. El resultat d'aplicar aquest filtre, un cop units els fotogrames com una seqüència amb un programa d'edició de vídeo, és el següent:

TimeLapse_PerTempsDinversions

PROGRAMES I FITXERS:

FITXERS D'ENTRADA

  • 1. fitxerInversions_inicial.xlsx El fitxer on introduim les dades.

  • 2. fitxerInversions_parsejatFinal.xlsx: El fitxer que es genera a partir de 1. fitxerInversions_inicial.xlsx, i que no cal tocar per a res (a més de ser un fitxer de sortida obtingut a partir de 1. fitxerInversions_inicial.xlsx també és un fitxer d'entrada per al codi, però l'usuari no l'ha de modificar).

FITXERS DE SORTIDA

  • out_Investors_orderedByNumberOfInvestments.txt: És un fitxer de sortida que conté TOTS els Investors ordenats de més a menys inversions (Investments) fetes. Útil per a aplicar el filtre que filtra per inversor i veure quins inversors val la pena representar en el graf perquè es generarin resultats amb sentit.

  • dic_nodePosicio_guardat.json: guarda les posicions dels nodes per recuperar-les cada cop que corres l'script per al timelapse (que, com s'ha dit abans, requereix un layout constant en totes les imatges). Si afegeixes files a l'excel probablement es destarotarà tot. Si això passa senzillament esborra aquest document .json manualment!

  • timelapse_Time_Difference/: carpeta on es guardaran els grafics time lapse quan es demani. No esborrar-la!

PROGRAMES

Llistat d'scripts

  • SSS.py: genera el graf a partir de l'excel 2. fitxerInversions_parsejatFinal.xlsx (prenent els diferents Investor(s) i Investment(s) com a nodes, fent arestes entre Investor(s) i Investment(s) i, finalment, passant com a propietat de les arestes els atributs Forecast_Sell_Date(data en què es van duplicar el valor de les accions després de comprar-les -o data prevista en que això passarà-) i Buy_Date (data en que es van adquirir les accions).

  • __main__grafGuay.py: Integra les funcions dels fitxers filtres.py i SSS.py i, també, permet modificar els paràmetres dels filtres i quin d'ells s'aplica cada vegada. És programa que cal executar per fer anar el programa -sempre que no fem cap canvi a l'excel d'entrada 1. fitxerInversions_inicial.xlsx- i que ens permet accedir a cada filtre sempre que eliminem els comentaris fets amb cometes triples als que no volem executar.

  • filtres.py: Hi ha les funcions per aplicar els filtres, que cridem des de __main__grafGuay.py

  • parseExcel.py: Aquest arxiu obre 1. fitxerInversions_inicial.xlsx i permet eliminar el grup "Investor:" que apareix en cada cel·la de columna del mateix nom, treure'n els espais per l'esquerra i la dreta2 i guardar la columna "Investor_parsejat" dins 2. fitxerInversions_parsejatFinal.xlsx. És aquesta última columna la que farà servir SSS.py per a fer el graf (dades que després passaran a __main__grafGuay.py per a la seva representació). Només caldrà executar parseExcel.py si feu canvis a 1. fitxerInversions_inicial.xlsx, per a què els canvis es tradueixin a 2. fitxerInversions_parsejatFinal.xlsx i després ja pogueu aplicar __main__grafGuay.py.

lògica de programació per fitxers

En aquest apartat explicaré i/o citaré les quatre funcions del fitxer mencionat: informaRepetits(), converteixAny_a_AnyMesDia(), guarda_o_recupera_coordenades() i, finalment, crea_grafic():

funció informaRepetits()

Dins d'aquest fitxer he programat diverses funcions que considero interessants d'explicar. La primera funció interessant a comentar és informaRepetits(). Aquesta funció emana de la problemàtica que es generà a SSS.py, on a les línies 25 i 26 els nodes d'investor i investment s'afegeixen al graf G de networkX i, alhora, en una llista per tenir-los a mà fàcilment a les línies 27 i 28:

for index, row in InvestorsData.iterrows():
G.add_node(row["Investment"]) #Forecast_Sell_Date=row["Forecast_Sell_Date"])
G.add_node(row["Investor_Parsejat"])
ll_investmentInvestor += [row["Investment"]]
ll_investmentInvestor += [row["Investor_Parsejat"]]

El problema és que un cop s'han introduit al graf els nodes de tipus inversor i de tipus inversió passen a formar part d'un tot homogeni i són indistingibles, ja que no existeix una forma de separar-los (cosa indispensable, perquè cada un d'ells requereix anotacions diferents i se'n requereix saber la naturalesa -inversor o inversió-). Inicialment, l'argument per trobar quin era quin era senzill: en recórrer G.adjacency() si un node s'havia afegit en una posició parell, hauria de ser un inversor; pel contrari, si s'havia afegit en una posició senar seria una inversió (ja que s'haurien anat afegint en ordre, com veiem a les línies 25 i 26 de l'anterior bloc de codi). El que passava és que aquesta assumpció no era correcta: per exemple, si existien inversions que es repetien en diverses files de l'excel (i.e. múltiples inversors que han invertit en una mateixa companyia o inversió) els nodes repetits s'eliminaven internament. Això es produia perquè la llibreria networkx implementa alguna estructura de dades per representar el graf que fa que dos nodes de dins el graf no puguin tenir el mateix nom (probablement un set o conjunt). Això va generar la necessitat de tenir en compte constants canvis de paritat en l'argument que feiem prèviament per classificar un node afegit com a inversor o inversió, per tal de rastrejar els canvis que es produeixen en la paritat a mesura que ens movem pel graf i passem per punts on nodes -repetits- han sigut eliminats o no afegits. Per tal de solventar-ho vaig fer la funció informaRepetits() que ens informava per a la llista ll_investmentInvestor (que obteniem del fitxer SSS.py, la passàvem al fitxer __main__grafGuay.py i podeu veure la seva creació a les línies 27 i 28 del codi previ) en quins punts de la llista hi havia elements repetits, ja que si els ubicàvem ens diria indirectament els canvis de paritat que es produien en afegir els nodes al graf. La funció informaRepetits() és la següent:

def informaRepetits(ll_investmentInvestor,imprimir):
"""
DESCRIPCIO_ Ens permet corregir el problema de paritat que es dóna en desapareixer els elements de l'excel un cop passats al graf G.
Absolutament essencial perquè les descripcions de Investor i Investment no es mesclin al graf.
PRE: ll_investmentInvestor <-- (variable global) : es la llista que conté les columnes "Investment i Investor_Parsejat" de l'excel processades
de FORMA ALTERNA i indexades desde zero (i.e [fila1_columnaInvestment, fila1_columnaInvestor, f2_cBT, f2_cBO, f3_cBT, f3_cBO, [...], filaN_columnaInvestment,
filaN_columnaInvestor]). A aquesta extracció l'anomerament la col·lecció d'elements excel. Noteu que ll_noms_dels_nodes conté aquesta col·lecció,
respectant-ne l'ordre, però amb els noms dels repetits eliminats. El problema d'això és que ens trenca l'argument de paritat, que ens ajuda a
decidir si un node és Investor o Investment, per tal de poder fer la llegenda hoverable del gràfic.
la funció retorna en quins indexos de "ll_noms_dels_nodes" (on nodes) caldrà fer un CANVI de paritat per
decidir aixi si es Investor o no.
imprimir <-- un boolea que, si es true, demana que imprimim el diccionari de ocurrencies multiples i el set de indexos a eliminar
POST: set_indexos_canvis_paritat --> un conjunt amb els indexos dels nodes en els quals caldrà modeficar la paritat per compensar les eliminacions que produeix fer el graf sense repetir nodes.
"""
j = 0
conjunt_rep = set() #un conjunt amb els elements repetits i com a valors llistes dels indexos on caldra fer el canvi de paritat a ll_noms_dels_nodes
d_canvis_paritat = {} #diccionari que contindrà com a claus els indexos en els quals, abans de processar el node, caldrà fer un canvi de paritat si el seu valor es IMPARELL (es un diccionari d'ocurrencies multiples que informa quants elements s'eliminen per davant i, per tant, d'aqui podem treure els canvis de paritat)
for i in range(len(ll_investmentInvestor)):
if ll_investmentInvestor[i] in conjunt_rep:
if j in d_canvis_paritat:
d_canvis_paritat[j] += 1
else:
d_canvis_paritat[j] = 1
j = j - 1
else:
conjunt_rep.add(ll_investmentInvestor[i])
j = j + 1
set_indexos_canvis_paritat = set() #el que retornarem
for clau in d_canvis_paritat:
if d_canvis_paritat[clau] % 2 != 0: #son els repetits que es repeteixen de forma imparell, i si generen canvis de paritat. Els unics que cal ajustar!
set_indexos_canvis_paritat.add(clau)
#RETORNEM només els indexos on cal canviar la paritat
if imprimir:
print("d_canvis_paritat:\n ",d_canvis_paritat)
print("set_indexos_canvis_paritat\n ",set_indexos_canvis_paritat)
return set_indexos_canvis_paritat

Fixeu-vos que la funció informaRepetits() ens retorna un conjunt (un set) anomenat set_indexos_canvis_paritat que conté els indexos de dins del graf G en els quals es produirien internament aquests canvis de paritat per eliminar o evitar afegir els nodes repetits. Fet això, esdevenia factible aplicar l'argument de paritat fet anteriorment per classificar cada node de dins del graf en un inversor o una inversió (vegeu linia 291 on carreguem set_indexos_canvis_paritat des de la funció on l'hem obtingut i, després, de la línia 303 a la 308 on apliquem l'argument de paritat ajustat per eliminats):

node_adjacencies = []
node_text = []
set_indexos_canvis_paritat = informaRepetits(ll_investmentInvestor,False)
correccio = 0
for node, adjacencies in enumerate(G.adjacency()):
node_adjacencies.append(len(adjacencies[1]))
"""A continuació, mitjançant argument de paritat aconseguim que només es mostrin les connexions per a l'Investor i que es
pugui diferenciar a la llegenda si el node es tracta d'Investment o d'Investor. Cal vigilar molt la paritat obtinguda a ll_investmentInvestor.
Aquest llista ve de l'excel i allà index parell es un Investment i l'index senar es un Investor. Cal prestar atenció a aquest detall perquè la
paritat queda destruïda en fer el graf... ja que dins el mateix s'eliminen els nodes repetits. Per tal de poder obtenir de nou la informació que la
destrucció d'aquesta paritat genera hem creat la funció informaRepetits, que ens diu en quins indexos de ll_noms_Dels_nodes
caldrà fer una correcció abans d'accedir a l'element."
"""
#CORREGIM LA PARITAT SI S'ESCAU IMPORTANTISSIM
if node in set_indexos_canvis_paritat:
correccio += 1
if (node + correccio) % 2 == 0:
node_text.append('<i>Investment:</i> <b>{}</b>'.format(ll_noms_dels_nodes[node])) #TROBAR AIXÒ HA COSTAT UN HUEVO
else:
node_text.append('<i>Number of investments:</i> {}<br><i>Investor:</i> <b>{}</b>'.format(len(adjacencies[1]), ll_noms_dels_nodes[node])) #TROBAR AIXÒ HA COSTAT UN HUEVO
node_trace.marker.color = node_adjacencies
node_trace.text = node_text

És clar que tot hagués sigut més senzill si haguessim pogut fixar les propietats corresponents dels nodes mentre els afegiem al graf. Però no va ser possible a partir del fitxer de codi que ens proporcionava l'empresa i la naturalesa de la llibreria.

funció converteixAny_a_AnyMesDia()

Aquesta funció és una funció estàndar per passar de temps en anys com a nombre real amb decimals (float) a tipus de dades string, amb formateig " A anys, M mesos i D dies", però en anglès. La funció la fem servir per tal de mostrar, en el requadre observable en passar per damunt de les arestes del graf, les diferències temporals entre data de compra i de duplicació de la inversió (diferència anomenada, com hem vist, Time_Difference):

def converteixAny_a_AnyMesDia(anyet):
#pre: anyet es un float o un string de la forma "12.257" (conté un nombre real d'anys)
#post: string de forma A anys, D mesos i d dies
anyet = float(anyet)
anyetPartEntera = int(anyet)
anyetMantisa = anyet - anyetPartEntera
mesos = anyetMantisa * 12
mesosPartEntera = int(mesos)
mesosMantisa = mesos - mesosPartEntera
diesPartEntera = int(mesosMantisa * 30.44)
return "{} year/s, {} month/s and {} day/s".format(anyetPartEntera, mesosPartEntera, diesPartEntera)

funció guarda_o_recupera_coordenades()

Aquesta funció ens permet guardar i recuperar les dades de les coordenades, per a fer els timelapse, en fitxers JSON:

def guarda_o_recupera_coordenades(dic_nodePosicio):
"""
DESCRIPCIÓ: en aquesta funcio permetem processar el dic_nodePosicio per fer-lo apte de guardar. Si no hi es el guarda
per recuperar-lo després i el retorna inalterat. Si hi és, el recupera i el retorna. Útil per fer un timelapse de gràfics.
PRE: el dic_nodePosicio amb les coordenades
POST: el diccionari node posicio amb les coordenades que estaven guardades (o amb les mateixes, segons s'escaigui)"""
#LLEGEIXO EL DICCIONARI DE COORDENADES SI JA HI ES, I SI NO HI ES EN CREO UN DE NOU
try:
#CARREGO EL DICT D'UN JSON GUARDAT
with open("dic_nodePosicio_guardat.json","r") as f:
dic_nodePosicio = json.load(f)
except:
#CAL CANVIAR LES COORDENADES NDARRAY A LIST. O SINO DUMP NO VA
for node in dic_nodePosicio:
dic_nodePosicio[node] = list(dic_nodePosicio[node])
print(dic_nodePosicio[node],type(dic_nodePosicio[node]))
#GUARDO EL DICT A UN json
with open("dic_nodePosicio_guardat.json","w") as f:
json.dump(dic_nodePosicio,f)
return dic_nodePosicio

funció crea_grafic()

Finalment, aquí tenim la funció crea_grafic() on s'integra tot:

def crea_grafic(fesFiltre, tipusFiltre, informacioFiltre):
"""
PRE: - fesFiltre: Booleà. Si és True anirà a mirar la resta de paràmetres per definir el filtre. En cas contrari la resta de paràmetres
no importaran i podran deixar-se com a string buit (tipusFiltre) i llista buida (informacioFiltre), respectivament.
- tipusFiltre: Un string que pot prendre quatre valors: "intervalForecast_Sell_Date", "Investor", "Time_Difference" o "timelapse_Time_Difference" (l'últim requereix posar la funció dins un while)
- informacioFiltre: és diferent en funció del paràmetre que pren tipus Filtre.
Si a tipusFiltre poses "Investor" -------------> dins "informacioFiltre" has de ficar una llista d'strings que contingui un o diversos Investors -strings- pels quals vulguis filtrar.
Si a tipusFiltre poses "intervalForecast_Sell_Date" --> dins "informacioFiltre" has de ficar dos enters: l'any inici i l'any final (limit superior i inferior) del filtre.
Si a tipusFiltre poses "Time_Difference" ------------> dins "informacioFiltre" has de ficar una llista d'strings que contingui un o diversos Time_Differences -strings- pels quals vulguis filtrar.
Si a tipusFiltre poses "timelapse_Time_Difference" -> dins "informacioFiltre" has de ficar dos enters: l'any inici i l'any final (limit superior i inferior) del filtre I TAMBÉ has de posar el codi dins un while, tal i com hem fet abaix de tot a l'exemple.
POST: - El gràfic obert al navegador.
- document txt (out_Investors_orderedByNumberOfInvestments.txt) amb els Investors, ordenats per la quantitat d'inversions que s'associen a cada un.
"""
#PORTO EL GRAF DE L'ALTRE FITXER I LES POSICIONS (coordenades) ASSIGNADES A CADA NODE
G, dic_nodePosicio, ll_investmentInvestor, Investor_series, Time_Difference_series, minMax_Forecast_Sell_Date = SSS.creaGraf() #ll_to
if tipusFiltre == "timelapse_Time_Difference":
dic_nodePosicio = guarda_o_recupera_coordenades(dic_nodePosicio) #FIXO EL DICCIONARI NODE POSICIÓ
limInf, limSup = minMax_Forecast_Sell_Date
#IMPRIMIM LES CONNEXIONS DE CADA Investor, ORDENADES DE MÉS A MENYS CONNEXIONS
print("\n###########################################")
#print(Investor_series.value_counts())
print("Forecast_Sell_Date ------------------> min: {} || max: {}. \n (Pots fer servir aquesta informació per decidir\n com filtrar quan filtres per Forecast_Sell_Date)".format(limInf, limSup))
print("llistat exhaustiu Investors --> out_Investors_orderedByNumberOfInvestments.txt")
print("###########################################\n")
#TREIEM LA INFORMACIÓ DELS VALUE COUNTS
Investors_de_mes_a_menys_connexions = list(Investor_series.value_counts().keys())
nombre_de_connexions = list(Investor_series.value_counts().values)
Time_Differences_ordPer_ocurrencies = list(Time_Difference_series.value_counts().keys())
Time_Differences_ocurrencies = list(Time_Difference_series.value_counts().values)
#GUARDEM EN UN TXT PER PODER CONSULTAR EN FER EL DESPLEGABLE DEL FILTRE DE InvestorS (TÉ MES VALOR SABER ELS QUE TENEN MES INVERSIONS PER FILTRAR-LOS)
with open("out_Investors_orderedByNumberOfInvestments.txt","w") as f:
for Investor, connexions, Time_Difference, sorcOcurr in zip(Investors_de_mes_a_menys_connexions, nombre_de_connexions, Time_Differences_ordPer_ocurrencies, Time_Differences_ocurrencies):
f.write(str(connexions) + "\t" + Investor + "\n")
#TREBALLEM AMB ELS NODES
node_x = []
node_y = []
ll_noms_dels_nodes = [] #afegim els noms dels nodes!
i = 0
d_noms_nodes = {} #fem un diccionari clau valor amb nom del node a l'index {"IBM": 1, Top 3 QC /QIS trends: 2} ...
for node in G.nodes(data=False): #afegeix data = true si vols accedir a atributs dels nodes
#SI VOLS ACCEDIR A PROPIETATS DELS NODES POSA TRUE A G.nodes i afegeix la seguent linia, i pots usar dic_Atributs
#node, dic_atributs = tupla_node #node es el nom del node
#print(node,dic_atributs) # {"Forecast_Sell_Date":2023, "asd":"blah",...} Pots afegir mes claus anant al fitxer SSS
#time.sleep(2)
x,y = dic_nodePosicio[node] #x, y = G.nodes[node]['pos'] CODI CANVIAT PER LA LINIA NO COMENTADA
node_x.append(x)
node_y.append(y)
ll_noms_dels_nodes += [node] #LINIA AFEGIDA
d_noms_nodes[node] = i #linia afegida. ens permetra trobar els indexos a afegir al grafic en que filtrarem per propietats del mateix
i = i + 1
#CREAR ARESTES
edge_x = []
edge_y = []
#SI FEM FILTRE CAL TRACTAR LES DADES DE FORMA DIFERENT
if fesFiltre:
ll_punts_seleccionats_filtre = [] #LLISTA AMB EL PUNTS DEL FILTRE
else:
ll_punts_seleccionats_filtre = "" #LA FEM STRING BUIT PERQUÈ EN PASSAR-HO A selectedpoints DE go.scatter() NO HI HAGI FILTRE
#NOTEU QUE edge[0] es un node i edge[1] es l'altre node (units per una aresta)
dic_etiquetes_edges = {}
for edge in G.edges(data = True):
#x0, y0 = G.nodes[edge[0]]['pos'] #AIXO HO HEM ESBORRAT PERQUÈ HEM NECESSITAT ACCEDIR A LES COORDENADES D'UNA ALTRA MANERA QUE AL CODI D'ORIGEN LINQUEJAT A LA CAPÇALERA DEL DOCUMENT
x0, y0 = (dic_nodePosicio[edge[0]][0], dic_nodePosicio[edge[0]][1])
x1, y1 = (dic_nodePosicio[edge[1]][0], dic_nodePosicio[edge[1]][1])#ABANS ERA: x1, y1 = G.nodes[edge[1]]['pos'] (IDEM A CANVI EN L'OBTENCIÓ de coordenades x0, y0
#CRIDEM A LA FUNCIO QUE ENS FA EL FILTRE DELS NODES (ACCEDINT A LES PROPIETATS DE LES EDGES, RETORNA ELS INDEXOS DELS NODES ALS QUALS LES EDGES SON INCIDENTS=
if fesFiltre:
if tipusFiltre == "intervalForecast_Sell_Date":
ll_punts_seleccionats_filtre += filtres.perAnysForecast_Sell_Date(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)#nota que edge permet fer ---> node1, node2, dic_propietats = edge
elif tipusFiltre == "Investor":
ll_punts_seleccionats_filtre += filtres.perInvestor(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
elif tipusFiltre == "Time_Difference":
ll_punts_seleccionats_filtre += filtres.perTime_Difference(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
elif tipusFiltre == "timelapse_Time_Difference":
ll_punts_seleccionats_filtre += filtres.perTime_Difference(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
else:
raise ValueError('El nom del filtre aplicat no correspon a cap filtre! Escull entre "Forecast_Sell_Date","technologies","Investor" o "Time_Difference"')
edge_x.append(x0)
edge_x.append(x1)
edge_x.append(None)
edge_y.append(y0)
edge_y.append(y1)
edge_y.append(None)
#AFEGIM ELS ANYS DE Forecast_Sell_Date A DINS LES EDGES CAL RESPECTAR LES ESTRUCTURES QUE DEMANA nx.draw_networkx_edge_labels
node1, node2, d_propNode = edge
dic_etiquetes_edges[(node1, node2)] = "{}::{}::{}".format(d_propNode["Forecast_Sell_Date"],
d_propNode["Buy_Date"],
d_propNode["Time_Difference"]) #FIQUEM ELS DOS NODES DE L'EDGE COM A CLAUS I COM A VALOR L'ANY DE Forecast_Sell_Date
#INFORMACIO COMPLEMENTARIA ALS FILTRES
if fesFiltre:
if tipusFiltre == "intervalForecast_Sell_Date":
anyIni, anyFi = informacioFiltre
if anyIni == anyFi:
infoFiltre = " ({})".format(anyIni) #o anyFi
else:
infoFiltre = " ({} to {})".format(anyIni,anyFi)
elif tipusFiltre == "Investor":
infoFiltre = " (filtered by Investor: {} Investor/s)".format(len(informacioFiltre))
elif tipusFiltre == "Time_Difference" or tipusFiltre == "timelapse_Time_Difference":
anyIni, anyFi = informacioFiltre
#CODI ADAPTAT COPIAT DE FILTRE PER ANYS (intervalForecast_Sell_Date per fer la versió adaptada)
if anyIni == anyFi:
infoFiltre = " ({} years needed)".format(anyIni) #o anyFi
else:
infoFiltre = " (between {} to {} years needed)".format(anyIni,anyFi)
else:
infoFiltre = ""
else:
infoFiltre = ""
#FER ELS GRAFICS
edge_trace = go.Scatter(
x=edge_x, y=edge_y,
line=dict(width=0.5, color='#888'),
hoverinfo='all', #NO FUNCIONA
mode='lines',
showlegend=False) #linia afegida perque no surti la llegenda dela trace
node_trace = go.Scatter(
x=node_x, y=node_y,
showlegend=False, #linia afegida perque no surti la llegenda dela trace
selectedpoints = ll_punts_seleccionats_filtre, #si ll_punts_seleccionats_filtre canvia a string buit no aplica filtre, aquesta es la estrategia que he pres per no filtrar
mode='markers',
hoverinfo='text',
#text=ll_noms_dels_nodes, #LINIA AFEGIDA!
marker=dict(
showscale=True,
# colorscale options
#'Greys' | 'YlGnBu' | 'Greens' | 'YlOrRd' | 'Bluered' | 'RdBu' |
#'Reds' | 'Blues' | 'Picnic' | 'Rainbow' | 'Portland' | 'Jet' |
#'Hot' | 'Blackbody' | 'Earth' | 'Electric' | 'Viridis' |
colorscale='Hot',
reversescale=True,
color=[],
size=6.5,
colorbar=dict(
thickness=13,
title='Number of connections',
xanchor='left',
titleside='right'
),
line_width=1))
#COLOREJAR
node_adjacencies = []
node_text = []
set_indexos_canvis_paritat = informaRepetits(ll_investmentInvestor,False)
correccio = 0
for node, adjacencies in enumerate(G.adjacency()):
node_adjacencies.append(len(adjacencies[1]))
"""A continuació, mitjançant argument de paritat aconseguim que només es mostrin les connexions per a l'Investor i que es
pugui diferenciar a la llegenda si el node es tracta d'Investment o d'Investor. Cal vigilar molt la paritat obtinguda a ll_investmentInvestor.
Aquest llista ve de l'excel i allà index parell es un Investment i l'index senar es un Investor. Cal prestar atenció a aquest detall perquè la
paritat queda destruïda en fer el graf... ja que dins el mateix s'eliminen els nodes repetits. Per tal de poder obtenir de nou la informació que la
destrucció d'aquesta paritat genera hem creat la funció informaRepetits, que ens diu en quins indexos de ll_noms_Dels_nodes
caldrà fer una correcció abans d'accedir a l'element."
"""
#CORREGIM LA PARITAT SI S'ESCAU IMPORTANTISSIM
if node in set_indexos_canvis_paritat:
correccio += 1
if (node + correccio) % 2 == 0:
node_text.append('<i>Investment:</i> <b>{}</b>'.format(ll_noms_dels_nodes[node])) #TROBAR AIXÒ HA COSTAT UN HUEVO
else:
node_text.append('<i>Number of investments:</i> {}<br><i>Investor:</i> <b>{}</b>'.format(len(adjacencies[1]), ll_noms_dels_nodes[node])) #TROBAR AIXÒ HA COSTAT UN HUEVO
node_trace.marker.color = node_adjacencies
node_trace.text = node_text
fig = go.Figure(data=[edge_trace, node_trace],
layout=go.Layout(
title='Investors and their time to double investment'+infoFiltre,
titlefont_size=16,
showlegend=True,
hovermode="closest", #https://plotly.com/python/reference/layout/#layout-hovermode NO PERMET AFEGIR MES DADES
margin=dict(b=20,l=5,r=5,t=40),
annotations=[ dict(
text="Python code: <a href='https://plotly.com/ipython-notebooks/network-graphs/'> https://plotly.com/ipython-notebooks/network-graphs/</a>",
showarrow=False,
xref="paper", yref="paper",
x=0.005, y=-0.002 ) ],
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
)
#BIB:
#https://stackoverflow.com/questions/47094949/labeling-edges-in-networkx
#https://networkx.org/documentation/stable/reference/generated/networkx.drawing.nx_pylab.draw_networkx_edge_labels.html
#nx.draw(G, dic_nodePosicio, edge_color='black', width=1, linewidths=1,node_size=10, node_color='blue', alpha=0.5,labels={node: node for node in G.nodes()})
dic_edge_posicionsText = nx.draw_networkx_edge_labels(G, dic_nodePosicio, edge_labels=dic_etiquetes_edges, font_color='red')
for tupla_edge in dic_edge_posicionsText:
#HEM DE TREURE L'ANY I LES POSICIONS DE L'ANY, PERQUE ESTA DINS UNA CLASSE MATPLOBLIB. CONSULTANT L'API HEM TROBAT
#COM EXTREURE-HO https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text.get_position
anyForecast_Sell_Date__anyPrediccio__Time_Difference = dic_edge_posicionsText[tupla_edge].get_text()
x_txt_edge, y_txt_edge = dic_edge_posicionsText[tupla_edge].get_position()
Forecast_Sell_Date, Buy_Date, Time_Difference = anyForecast_Sell_Date__anyPrediccio__Time_Difference.split("::")
# AFEGIM ELS ANYS DE Forecast_Sell_Date A LES EDGES QUE UNEXIEN L'Investor AMB LA TECH EN CONCRET. NO ES POT FER HOVERABLE, PERÒ MILLOR
# AIXI QUE NO PAS AFEGIR-HO ALS NODES DE TECH, CAS EN QUE SI DOS InvestorS APUNTEN A UNA SOLA TECH NO ES PODRIA VEURE QUIN ANY
# DE Forecast_Sell_Date ELS PERTOCA A CADA UN D'ELLS...
anyActual = datetime.datetime.now().year
if anyActual > int(Forecast_Sell_Date):
etiqueta = "[Investment x2]" #Sell Date a la qual la inversió es va duplicar (cas que va ocórrer)
else:
etiqueta = "Forecast [Investment x2]" #Forecast Sell Date ()
fig.add_annotation(
text=" ", #https://plotly.com/python/text-and-annotations/#:~:text=Annotations%20can%20be%20added%20to%20a%20figure%20using,rendering%20the%20information%2C%20and%20will%20override%20textinfo%20.
hovertext="Buy Date: {}<br>{}: {}<br>Time Difference: {}".format(Buy_Date, etiqueta, Forecast_Sell_Date, converteixAny_a_AnyMesDia(Time_Difference)),
x=x_txt_edge,
y=y_txt_edge,
xref="x",
yref="y",
showarrow=False
)
if tipusFiltre == "timelapse_Time_Difference":
li, ls = informacioFiltre[0], informacioFiltre[1]
fig.write_image('timelapse_Time_Difference/{}_to_{}_years.jpeg'.format(li,ls), scale=7)
else:
fig.show()

funcions dins de filtres.py**

Les tres primeres funcions que especifico són molt similars: Tant perAnysForecast_Sell_Date(), com perInvestor(), com perTime_Difference() es van aplicant de forma iterativa per a cada edge. Com podeu veure a continuació La llista ll de sortida de cada una d'aquestes funcions-filtre és en realitat una actualització del paràmetre d'entrada ll_punts_seleccionats_filtre que conté els indexos dels nodes que volem filtrar, i que es van guardant a la llista del mateix nom ll_punts_seleccionats_filtre ubicada dins del fitxer __main__grafGuay.py:

#CRIDEM A LA FUNCIO QUE ENS FA EL FILTRE DELS NODES (ACCEDINT A LES PROPIETATS DE LES EDGES, RETORNA ELS INDEXOS DELS NODES ALS QUALS LES EDGES SON INCIDENTS=
if fesFiltre:
if tipusFiltre == "intervalForecast_Sell_Date":
ll_punts_seleccionats_filtre += filtres.perAnysForecast_Sell_Date(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)#nota que edge permet fer ---> node1, node2, dic_propietats = edge
elif tipusFiltre == "Investor":
ll_punts_seleccionats_filtre += filtres.perInvestor(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
elif tipusFiltre == "Time_Difference":
ll_punts_seleccionats_filtre += filtres.perTime_Difference(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
elif tipusFiltre == "timelapse_Time_Difference":
ll_punts_seleccionats_filtre += filtres.perTime_Difference(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre)
else:
raise ValueError('El nom del filtre aplicat no correspon a cap filtre! Escull entre "Forecast_Sell_Date","technologies","Investor" o "Time_Difference"')

De fet, tant les funcions perTime_Difference() com perAnysForecast_Sell_Date() s'implementen de forma molt similar. Només varia el nom de la clau que li passem al diccionari d_prop_node. La funció perInvestor() té un petit canvi: no aplica filtres per interval sino per coincidència exacta, ja que no tracta amb dades numèriques com l'anterior sino amb fitxers de tipus string. Finalment, la funció timelapse_Time_Difference() implementa crides iterartives a perTime_Difference(). A continuació teniu un enllaç a totes elles:

funció perAnysForecast_Sell_Date()

def perAnysForecast_Sell_Date(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre):
"""
ARGUMENTS: - ll_punts_seleccionats_filtre: Una llista que es o be buida o bé conte punts que es van afegints al filtre en funcio de les propietats demanades.
- d_noms_nodes: fem un diccionari clau valor amb nom del node a l'index que ocupa dins de G.nodes() {"Elon Musk": 1, "Bitcoin": 2} ...
- edge: una tupla que, desempaquetada, dona lloc a ---> node1, node2, d_propNode = edge <---: es a dir els noms dels dos nodes units per la
edge i un diccionari amb les pripietats que hi ha definides per a aquesta edge.
Una d'aquestes propietats, "Forecast_Sell_Date", conté el moment per al qual es fa la predicció o la descripció de x2 en la inversió.
- informacioFiltre: una llista amb dos enters que contenen els anys entre els quals vols fer el filtre. Si son iguals nomes filtra l'any concret.
RETURNS: - ll: afegim un o dos enters a la llista ll, que són (i la llista conté) indexos dels nodes que després voldrem filtrar per any.
"""
node1, node2, d_propNode = edge
anyInicial, anyFinal = informacioFiltre
ll = []
#APLIQUEM EL FILTRE AL NODE CONCRET:
if anyInicial <= d_propNode["Forecast_Sell_Date"] <= anyFinal:
#EVALUEM SI ELS NODES JA SON A LA LLISTA FILTRADA (NO CAL REPETIR-LOS)
if not d_noms_nodes[node1] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node1]]
if not d_noms_nodes[node2] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node2]]
return ll

funció perInvestor()

def perInvestor(ll_punts_seleccionats_filtre, d_noms_nodes, edge, ll_Investor):
"""
ARGUMENTS: - ll_punts_seleccionats_filtre: Una llista que es o be buida o bé conte punts que es van afegints al filtre en funcio de les propietats demanades.
- d_noms_nodes: fem un diccionari clau valor amb nom del node a l'index que ocupa dins de G.nodes() {"Elon Musk": 1, "Bitcoin": 2} ...
- edge: una tupla que, desempaquetada, dona lloc a ---> node1, node2, d_propNode = edge <---: es a dir els noms dels dos nodes units per la
edge i un diccionari amb les pripietats que hi ha definides per a aquesta edge.
Una d'aquestes propietats, "Forecast_Sell_Date", conté el moment per al qual es fa la predicció de x2 en la inversió.
- ll_Investor: Una llista amb elements string de l'inversor o inversors que volem filtrar (coincidencia exacta).
RETURNS: - ll: afegim un o dos enters a la llista ll, que conté els indexos dels nodes que després voldrem filtrar. En aquest cas
Filtrem per Investor pero també filtrem per les technologies incidents a aquest Investor.
"""
node1, node2, d_propNode = edge
ll = []
for str_Investor in ll_Investor:
#APLIQUEM EL FILTRE AL NODE CONCRET:
if node1 == str_Investor or node2 == str_Investor:
#EVALUEM SI ELS NODES JA SON A LA LLISTA FILTRADA (NO CAL REPETIR-LOS)
if not d_noms_nodes[node1] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node1]]
if not d_noms_nodes[node2] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node2]]
return ll

funció perTime_Difference()

def perTime_Difference(ll_punts_seleccionats_filtre, d_noms_nodes, edge, informacioFiltre):
"""
Idem als altres filtres, però per a anys relatius.
"""
node1, node2, d_propNode = edge
anyInicial, anyFinal = informacioFiltre
ll = []
#APLIQUEM EL FILTRE AL NODE CONCRET:
if anyInicial <= d_propNode["Time_Difference"] <= anyFinal:
#EVALUEM SI ELS NODES JA SON A LA LLISTA FILTRADA (NO CAL REPETIR-LOS)
if not d_noms_nodes[node1] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node1]]
if not d_noms_nodes[node2] in ll_punts_seleccionats_filtre:
ll += [d_noms_nodes[node2]]
return ll

funció timelapse_Time_Difference()

def timelapse_Time_Difference(li,ls):
"""
NOTA:AQUEST ÉS UN TIPUS DE FILTRE ESPECIAL, PERQUÈ GENERA MÚTLTIPLES FILTRES PER INTERVALS DE Forecast_Sell_Date!
PRE: li, ls son el limit inferior i limit superior d'anys entre els quals vols fer el timelapse.
POST: produira una imatge per cada any en l'interval tancat [li,ls]. També esborra els json i imatges de timelapses previs.
"""
#ESBORRO JSONS PREVIS
if "dic_nodePosicio_guardat.json" in os.listdir():
os.remove("dic_nodePosicio_guardat.json")
print("NOTA: eliminem el fitxer json i en crearem un de nou per a tot el timelapse")
os.chdir("./timelapse_Time_Difference")
ll_imgs_grafs = os.listdir()
#ESBORRO IMATGES PREVIES
if len(ll_imgs_grafs) != 0:
print("borrem imatges antigues del timelapse previ!")
time.sleep(3)
for nomImatge in ll_imgs_grafs:
os.remove(nomImatge)
os.chdir("../")
#GENERO L'ITERADOR PER A FER ELS TIMELAPSES
i = li
while i <= ls:
crea_grafic(fesFiltre = True, tipusFiltre="timelapse_Time_Difference", informacioFiltre=[li,i])
i = i + 1

FER SERVIR EL TEU PROPI DATASET

En cas que vulgueu fer servir aquesta eina amb el vostre propi dataset heu de fer servir una serie de passos i respectar unes normes, per tal de que el codi produeixi un graf com el que hem vist aquí. A continuació les teniu:

  • PAS 1 Substituir l'excel "1. fitxerInversions_inicial" pel teu fitxer.

  • PAS 2 Caldrà que parsejis la columna "Investor" fent que hi hagi dos punts entre "investor" i la informació real de l'"Investment". Un cop fet això necessitaràs una nova columna anomenada "Investor_parsejat", sense espais per davant ni darrera a cada cel·la. Per fer-la pots fer servir l'arxiu "parseExcel.py" que te la generarà. Per tal d'aconseguir-ho executa'l un cop hagis fet canvis a "1. fitxerInversions_inicial.xlsx", cosa que et generarà una versió actualitzada de "2. Investor_parsejat.xlsx" amb la nova columna que necessites.

    · PAS 2.1 Assegura't que NO existeix cap cel·la de la columna "Investor_parsejat" que sigui exactament igual a qualsevol cel·la de la columna "Investment". Si això passés, els strings que defineixen aquestes cel·les acabarien definint un sol node al graf G i aleshores només es representarien com un punt quan haurien de sortir com a punts separats.

    · PAS 2.2 Assegura't també que NO HI HA CEL·LES BUIDES a Investor i Investment. Si n'hi han es canvien per NaN i tots els NaN networkx els interpreta com un sol node.

  • PAS 3 interpretar arxiu __main__grafGuay.py. Per definir els filtres cal anar a la part de baix de tot el document, fins "if name == "main". Hi ha diverses crides de la funció crea_grafic(), que es on es defineixen els filtres o l'absència dels mateixos en funció dels paràmetres que hi entren. Existeixen regles concretes per emplenar aquests paràmetres: per saber-les veure el docstring de la funció crea_grafic(). Cal que hi hagi una sola crida a la funció cada vegada, mantingues les altres comentades.

  • PAS 4 (opcional): si voleu crear timelapses (necessiteu guardar les imatges) cal baixar el paquet fent pip install -U kaleido. Definiu el limit inferior (li) i superior i correu el gràfic. Presteu atenció al print en pantalla que indica quin es el temps més gran que a un inversor ha dut duplicar la seva inversió a la columna Time_Difference Podreu fer-lo servir per definir el (ls) del grafic. Podeu fer un gràfic de 0 a 3 anys, per veure els inversors i les inversions que menys temps han tardat a fer inversions que es dupliquessin en valor.

AUTORIA I DADES DE CONTACTE

Programa fet per Santiago Sánchez Sans, Analista de dades amb Python. Podeu contactar-me a:

Footnotes

  1. Noteu que aquests filtres s'apliquen de forma independent (i.e. no pots aplicar dos filtres a l'hora, o dos filtres en sèrie).

  2. Els espais ha calgut eliminar-los perquè el que feien en el fitxer proporcionat per l'empresa era que __main__grafGuay.py mal funcionés i considerés el mateix inversor (un sol node) com si fossin diferents nodes o inversors.

About

This is a freelance project I made for a tech consultancy in Barcelona. I was asked to implement a network-graph data visualization tool for an internal corporate application (project description in Catalan)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages