bytes.cat

La wiki d'FP d'informàtica

Eines de l'usuari

Eines del lloc


opendata_pandas

Diferències

Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.

Enllaç a la visualització de la comparació

Ambdós costats versió prèvia Revisió prèvia
Següent revisió
Revisió prèvia
opendata_pandas [2023/07/01 14:25]
miquel_angel_amoros [Conclusions]
opendata_pandas [2023/07/04 23:23] (actual)
miquel_angel_amoros [Per què triem Pandas ?]
Línia 8: Línia 8:
 En aquesta guia veurem com obtenir dades obertes d'un servei web i guardar-les, per tal d'utilitzar-les a la nostra aplicació. Aquesta tècnica es coneix com a web scrapping. En aquesta guia veurem com obtenir dades obertes d'un servei web i guardar-les, per tal d'utilitzar-les a la nostra aplicació. Aquesta tècnica es coneix com a web scrapping.
  
-Per aconseguir-ho, en primer lloc provarem Pandas en un programa de Python local i/o amb Jupyter Notebook. Posteriorment, incorporarem el codi d'exemple de Pandas en una aplicació web feta amb Django o Flask.+Per aconseguir-ho, en primer lloc provarem Pandas en un programa de Python local i/o amb Jupyter Notebook.  
 + 
 +Posteriorment, incorporarem el codi d'exemple de Pandas en una aplicació web feta amb Django. Una bona opció és utiltizar la funcionalitat del seeder que ens ofereix Django per inserir automàticament els equips. 
 + 
 +<WRAP center round important 90%> 
 +Només s'hauria d'usar aquesta guia per a obtenir dades obertes i públiques; sinó ens podriem trobar amb problemes ètics i legals. 
 +</WRAP>
  
 ======= Per què triem Pandas ? ======= ======= Per què triem Pandas ? =======
  
-  * Ens proporciona una col·lecció molt útil, inexistent a Python, el DataFrame. És una taula bidimensional que podem indexar amb la/les columna/es que més ens interessin en cada monment (com a SQL).+  * Ens proporciona una col·lecció molt útil, inexistent a Python, el DataFrame. És una taula bidimensional que podem indexar amb la/les columna/es que més ens interessin en cada moment (com a SQL).
   * Pandas detecta automàticament inconsistències de tipus i contingut en les dades. Aconseguim avantatges similars als llenguatges de tipat fort (C, Java).   * Pandas detecta automàticament inconsistències de tipus i contingut en les dades. Aconseguim avantatges similars als llenguatges de tipat fort (C, Java).
   * Ens permet tractar grans volums de dades (superiors a 10.000 lines) amb molts mètodes per filtrar la informació.   * Ens permet tractar grans volums de dades (superiors a 10.000 lines) amb molts mètodes per filtrar la informació.
Línia 18: Línia 24:
   * Si organitzem les dades del Dataframe convenientment podem crear gràfics amb llibreries com Matplotlib o Seaborn, amb menys codi que si el posessim en llistes o diccionaris.   * Si organitzem les dades del Dataframe convenientment podem crear gràfics amb llibreries com Matplotlib o Seaborn, amb menys codi que si el posessim en llistes o diccionaris.
   * També facilita la feina si usem llibreries de Machine Learning.   * També facilita la feina si usem llibreries de Machine Learning.
-  * És una molt bona alternativa al llenguatge R, amb una corba d'aprentatge inferior si tenim coneixements bàsics de Python. 
- 
 ======= Instal·lació llibreria Pandas ======= ======= Instal·lació llibreria Pandas =======
  
Línia 35: Línia 39:
  
 Exemple: Volem obtenir el llistat dels equips de la lliga de futbol femení 2022-2023, que hem trobat a: Exemple: Volem obtenir el llistat dels equips de la lliga de futbol femení 2022-2023, que hem trobat a:
 +
 https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html
  
 En aquest cas potser acabem abans copiant i enganxant en un full de càlcul els resultats; però a la hora de la veritat no sempre funciona.  En aquest cas potser acabem abans copiant i enganxant en un full de càlcul els resultats; però a la hora de la veritat no sempre funciona. 
 +
 A més a més, ens interessa automatitzar les tasques.  A més a més, ens interessa automatitzar les tasques. 
  
-Pas 1. Obtenir dades+**Exemple codi local, arrencat en un fitxer IPYNB**
  
-<code>+El codi que mostro a continuació el podeu veure arrencat en directe a: 
 + 
 +https://colab.research.google.com/drive/1vKHXmy5e9KFGv3EnRHwJpzeHTP5zp6I5#scrollTo=ROmuDJnMryJL 
 + 
 +[[https://jupyter.org/try-jupyter/retro/notebooks/?path=notebooks/Intro.ipynb|JuPyTeR (Julia, Python i R) Notebook]] és un servei que permet executar codi i llibreries Python al navegador o  
 +en qualsevol IDE, fins i tot llibreries per generar gràfics. Els seus fitxers tenen la extensió **.IPYNB**     
 + 
 +**Pas 1. Seleccionar la font de dades.** 
 + 
 +En el nostre cas, dins de la pàgina indicada la taula que ens interessa té un atribut HTML "summary" amb el  
 +valor "Clasificación".  
 +El mètode read_html ha de conèixer la pàgina i atributs html per identificar la taula 
 +que volem extreure. 
 + 
 +**Pas 2. Obtenir les dades amb Pandas** 
 + 
 +<code python>
 import pandas as pd import pandas as pd
 from io import StringIO from io import StringIO
Línia 58: Línia 80:
 </code> </code>
  
-Pas 2. Filtrar dades i obtenir resultats.+**Pas 3. Filtrar dades i obtenir resultats.** 
 + 
 +<code python> 
 +# Seleccionem la llista dels noms dels equips. 
 +classificacio_df['EQUIPO'] = classificacio_df['EQUIPO'].astype("string"
 +list_aux = classificacio_df['EQUIPO'].values.tolist() 
 +llista_equips = sum(list_aux, [])
  
-<code> 
-# Visualitzem informació rellevant de les dades. 
 def print_data(): def print_data():
-  print(classificacio_df)+  #print(classificacio_df)
   print(classificacio_df.dtypes)   print(classificacio_df.dtypes)
-  print(classificacio_df.EQUIPO) +  print(classificacio_df['EQUIPO']
 +  print(llista_equips) 
 +        
 print_data() print_data()
 </code> </code>
  
  
-======= Exemple a Jupyter Notebook ======= 
  
-https://colab.research.google.com/drive/1vKHXmy5e9KFGv3EnRHwJpzeHTP5zp6I5#scrollTo=NkAqvLeGc8Jg+======= Codi integrat a la aplicació web amb Django =======
  
-Pendent millorar.+**Pas 4Inserim el codi que hem comprovat a la aplicació django, dins de l'script seeder.**
  
 +En primer lloc, posem el codi que hem provat abans:
  
-======= Exemple aplicació web =======+<code python> 
 +     def handle(self, *args, **options):
  
-Pendent+        print("Obtenim dades equips, des d'un portal Open Data"
 +        # https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html 
 +        # Web scrapping: obtenció de dades externes al nostre sistema. 
 +        url: str = "https://www.sport.es/deportes/futbol/liga-femenina-futbol/clasificacion-liga.html" 
 +        classif: pd.DataFrame = pd.read_html(url, attrs={"summary": "Clasificación"}) 
 +        # Per algún motiu que desconec la informació que m'interessa me la posa a  
 +        # la primera posició d'una llista. 
 +        classificacio_df = classif[0] 
 +        # Seleccionem la llista dels noms dels equips. 
 +        classificacio_df['EQUIPO'] = classificacio_df['EQUIPO'].astype("string"
 +        list_aux = classificacio_df['EQUIPO'].values.tolist() 
 +        llista_equips = sum(list_aux, []) 
 +        print(llista_equips) 
 +        print("Dades equips obtingudes correctament.")
  
-======= Conclusions =======+        #print(jugador=Jugadora.objects.get(dorsal=5)) 
 +        titol_lliga options['titol_lliga'][0] 
 +        ... 
 +</code>
  
-Pendent+Després del codi que crea la lliga si no existeix, comento el codi de creació d'equips del Faker 
 +i poso aquest, que crea i guarda els equips pel seu nom:
  
 +<code python>
 +        for nom_equip in llista_equips:
 +            equip = Equip(nom=nom_equip)
 +             #print(equip)
 +            equip.save()
 +            lliga.equips.add(equip)
  
-<WRAP center round important 60%> +        # Seleccionarem algunes jugadores. 
-DisclaimerNomés s'hauria d'usar aquesta guia per a obtenir dades obertes i públiques; sinó ens podriem trobar amb problemes ètics i legals+            print("Creem jugadores de l'equip "+nom_equip) 
-</WRAP>+            ..
 +</code>
  
-<WRAP center round tip 60%> 
-Menys parlar de futbol a tota hora i més accessibilitat al accedir a les dades generades. 
-</WRAP> 
  
 +--> Codi complet del seeder i el model #
 +Aquí podreu veure un exemple del codi de l'script i també el model.py que s'utilitza. 
 +
 +<file python /management/commands/crea_lliga.py>
 +from django.db import models
 +
 +# Observació: Django genera per defecte camps id autonumèrics
 +# com a clau primària. Hi ha formes de canviar aquest 
 +# comportament però per ara ens sembla OK.
 +
 +# Hauria de posar una relació ManyToMany per tal que un mateix equip
 +# pugui compatir en diverses lligues en un any (Champions)
 +# equips = models.ManyToManyField(Equip)
 +class Equip(models.Model):
 +    nom = models.CharField(max_length=100)
 +    ciutat = models.CharField(max_length=100,null=True)
 +    estadi = models.CharField(max_length=100,null=True)
 +    #lliga = models.ForeignKey(Lliga, on_delete=models.CASCADE)
 +    def __str__(self):
 +        return self.nom
 +
 +
 +class Lliga(models.Model):
 +    nom_temporada = models.CharField(max_length=100)
 +    equips = models.ManyToManyField(Equip)
 +    def __str__(self):
 +        return self.nom_temporada
 +    class Meta:
 +        verbose_name_plural = 'Lligues'
 +
 +
 +class Jugadora(models.Model):
 +    nom = models.CharField(max_length=100)
 +    cognom = models.CharField(max_length=100)
 +    dorsal = models.IntegerField()
 +    equip = models.ForeignKey(Equip, on_delete=models.CASCADE)
 +
 +    def __str__(self):
 +        return f'{self.dorsal} - {self.nom}'
 +    class Meta:
 +        verbose_name_plural = 'Jugadores'
 +
 +
 +class Partit(models.Model):
 +    class Meta:
 +        unique_together = ["local","visitant","lliga"]
 +    local = models.ForeignKey(Equip,on_delete=models.CASCADE,
 +                    related_name="partits_local",null=True,blank=True)
 +    visitant = models.ForeignKey(Equip,on_delete=models.CASCADE,
 +                    related_name="partits_visitant",null=True,blank=True)
 +    lliga = models.ForeignKey(Lliga,on_delete=models.CASCADE)
 +    detalls = models.TextField(null=True,blank=True)
 +    inici = models.DateTimeField(null=True,blank=True)
 +    def __str__(self):
 +        return "{} - {}".format(self.local,self.visitant)
 +    def gols_local(self):
 +        return self.event_set.filter(
 +            tipus=Event.EventType.GOL,equip=self.local).count()
 +    def gols_visitant(self):
 +        return self.event_set.filter(
 +            tipus=Event.EventType.GOL,equip=self.visitant).count()
 +
 +
 +class Event(models.Model):
 +    # Solució subòptima i provisional.
 +    # tipus_event = models.CharField(max_length=100)
 +    # el tipus d'event l'implementem amb algo tipus "enum"
 +    class EventType(models.TextChoices):
 +        GOL = "GOL"
 +        AUTOGOL = "AUTOGOL"
 +        FALTA = "FALTA"
 +        PENALTY = "PENALTY"
 +        MANS = "MANS"
 +        CESSIO = "CESSIO"
 +        FORA_DE_JOC = "FORA_DE_JOC"
 +        ASSISTENCIA = "ASSISTENCIA"
 +        TARGETA_GROGA = "TARGETA_GROGA"
 +        TARGETA_VERMELLA = "TARGETA_VERMELLA"
 +    partit = models.ForeignKey(Partit,on_delete=models.CASCADE)
 +    temps = models.TimeField()
 +    tipus = models.CharField(max_length=30,choices=EventType.choices)
 +    jugador = models.ForeignKey(Jugadora,null=True,
 +                    on_delete=models.SET_NULL,
 +                    related_name="events_fets")
 +    equip = models.ForeignKey(Equip,null=True,
 +                    on_delete=models.SET_NULL)
 +    # per les faltes
 +    jugador2 = models.ForeignKey(Jugadora,null=True,blank=True,
 +                    on_delete=models.SET_NULL,
 +                    related_name="events_rebuts")
 +    detalls = models.TextField(null=True,blank=True)
 +    def __str__(self):
 +        return f'{self.EventType} - {self.jugador}'
 +</file>
 +
 +
 +<file python models.py>
 +from django.db import models
 +
 +# Observació: Django genera per defecte camps id autonumèrics
 +# com a clau primària. Hi ha formes de canviar aquest 
 +# comportament però per ara ens sembla OK.
 +
 +# Hauria de posar una relació ManyToMany per tal que un mateix equip
 +# pugui compatir en diverses lligues en un any (Champions)
 +# equips = models.ManyToManyField(Equip)
 +class Equip(models.Model):
 +    nom = models.CharField(max_length=100)
 +    ciutat = models.CharField(max_length=100,null=True)
 +    estadi = models.CharField(max_length=100,null=True)
 +    #lliga = models.ForeignKey(Lliga, on_delete=models.CASCADE)
 +    def __str__(self):
 +        return self.nom
 +
 +
 +class Lliga(models.Model):
 +    nom_temporada = models.CharField(max_length=100)
 +    equips = models.ManyToManyField(Equip)
 +    def __str__(self):
 +        return self.nom_temporada
 +    class Meta:
 +        verbose_name_plural = 'Lligues'
 +
 +
 +class Jugadora(models.Model):
 +    nom = models.CharField(max_length=100)
 +    cognom = models.CharField(max_length=100)
 +    dorsal = models.IntegerField()
 +    equip = models.ForeignKey(Equip, on_delete=models.CASCADE)
 +
 +    def __str__(self):
 +        return f'{self.dorsal} - {self.nom}'
 +    class Meta:
 +        verbose_name_plural = 'Jugadores'
 +
 +
 +class Partit(models.Model):
 +    class Meta:
 +        unique_together = ["local","visitant","lliga"]
 +    local = models.ForeignKey(Equip,on_delete=models.CASCADE,
 +                    related_name="partits_local",null=True,blank=True)
 +    visitant = models.ForeignKey(Equip,on_delete=models.CASCADE,
 +                    related_name="partits_visitant",null=True,blank=True)
 +    lliga = models.ForeignKey(Lliga,on_delete=models.CASCADE)
 +    detalls = models.TextField(null=True,blank=True)
 +    inici = models.DateTimeField(null=True,blank=True)
 +    def __str__(self):
 +        return "{} - {}".format(self.local,self.visitant)
 +    def gols_local(self):
 +        return self.event_set.filter(
 +            tipus=Event.EventType.GOL,equip=self.local).count()
 +    def gols_visitant(self):
 +        return self.event_set.filter(
 +            tipus=Event.EventType.GOL,equip=self.visitant).count()
 +
 +
 +class Event(models.Model):
 +    # Solució subòptima i provisional.
 +    # tipus_event = models.CharField(max_length=100)
 +    # el tipus d'event l'implementem amb algo tipus "enum"
 +    class EventType(models.TextChoices):
 +        GOL = "GOL"
 +        AUTOGOL = "AUTOGOL"
 +        FALTA = "FALTA"
 +        PENALTY = "PENALTY"
 +        MANS = "MANS"
 +        CESSIO = "CESSIO"
 +        FORA_DE_JOC = "FORA_DE_JOC"
 +        ASSISTENCIA = "ASSISTENCIA"
 +        TARGETA_GROGA = "TARGETA_GROGA"
 +        TARGETA_VERMELLA = "TARGETA_VERMELLA"
 +    partit = models.ForeignKey(Partit,on_delete=models.CASCADE)
 +    temps = models.TimeField()
 +    tipus = models.CharField(max_length=30,choices=EventType.choices)
 +    jugador = models.ForeignKey(Jugadora,null=True,
 +                    on_delete=models.SET_NULL,
 +                    related_name="events_fets")
 +    equip = models.ForeignKey(Equip,null=True,
 +                    on_delete=models.SET_NULL)
 +    # per les faltes
 +    jugador2 = models.ForeignKey(Jugadora,null=True,blank=True,
 +                    on_delete=models.SET_NULL,
 +                    related_name="events_rebuts")
 +    detalls = models.TextField(null=True,blank=True)
 +    def __str__(self):
 +        return f'{self.EventType} - {self.jugador}'
 +</file>
 +
 +<--
 +
 +Resultat final, quan arrenquem la aplicació:
 +
 +Si heu canviat el model, primer executeu:
 +
 +<code>
 +(env) nomusuari@linux:~/lligafemprj$ python manage.py makemigrations lliga
 +(env) nomusuari@linux:~/lligafemprj$ python manage.py migrate
 +</code>
 +
 +Per omplir les dades de l'script seeder:
 +
 +<code>
 +(env) nomusuari@linux:~/lligafemprj$ ./manage.py crea_lliga <nom_lliga>
 +(env) nomusuari@linux:~/lligafemprj$ ./manage.py runserver
 +(env) nomusuari@linux:~/lligafemprj$ firefox http://127.0.0.1:8080/admin
 +</code>
 +
 +Si anem a veure la taula de partits podem comprovar que s'han generat correctament, juntament amb els gols 
 +dels locals i visitants.
 +
 +{{:panell_admin_opendata.jpg?600|}}
 +
 +======= Conclusions =======
  
 +Publicar i fer ús de dades públiques d'interès general a nivell de cultura, salut, economia, ... és un encert si en fem un bon ús i estem ben formats amb llenguatges com Python que ens faciliten aquesta tasca. 
  
  
opendata_pandas.1688221547.txt.gz · Darrera modificació: 2023/07/01 14:25 per miquel_angel_amoros