Persistencia de datos a través de archivos

Programación funcional y reactiva - Computación

Profesor: Ing. Santiago Quiñones

Docente Investigador

Departamento de Ingeniería Civil

Contenidos

Archivos CSV

Archivos CSV

  • Comma separated values
  • Formato abierto para representar tablas
    • Columnas se separan por , o ;
    • Filas por saltos de línea.
  • RFC 4180 (https://datatracker.ietf.org/doc/html/rfc4180)
  • Consideraciones:
    • No se especifica el juego de caracteres
    • El texto debe ir entre comillas "" si contiene espacios. Ejemplo: "Ejemplo de texto", 123, 456
    • Secuencias de escape: "Ejemplo con ""comillas""",123,456
  • Ejemplos de archivos para clases (descargar)

Descripción

Archivos CSV y Scala

Ejemplo

Año,Marca,Modelo,Descripción,Precio
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevyr,Venture,Extended Edition,4900.00
1999,Chevy,Venture,"Extended Edition, Very Large",5000.00
1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00

Archivos CSV y Scala

 

kantan.csv (https://nrinaudo.github.io/kantan.csv/)

kantan compatible RFC 4180

Resultados Right o Left

 

 

Librería

scalaVersion := "2.13.12" 
// Core library 
libraryDependencies += "com.nrinaudo" %% "kantan.csv" % "0.6.1"
import kantan.csv._
import kantan.csv.ops._
// import kantan.csv.generic._
import java.io.File

Archivos CSV y Scala

Leer filas como colección

val path2DataFile = "TemperaturasPromedioDecadas.csv"
val dataSource = new File(path2DataFile).asCsvReader[List[Int]](rfc)
dataSource.foreach(println _)
1980, 23, 13, 10, 22, 27, 6, 12, 16, 15, 16, 28, 15
1990, 5, 14, 14, 24, 16, 5, 15, 4, 5, 27, 5, 25
2000, 24, 11, 20, 13, 22, 12, 5, 15, 7, 17, 5, 8
2010, 18, 16, 6, 13, 20, 18, 21, 24, 13, 17, 18, 7
2020, 7, 7, 15, 7, 11, 14, 28, 20, 13, 23, 20, 8

Cada fila se leerá como una lista de números

enteros

para aplicar reglas del RFC 4180

No todos son números 

Muchos CSV contienen varios tipos de datos, incluyendo nombres de columnas.

Kantan

Leer filas como tuplas

val path2DataFile2 = "EstudioPoblacionLojaGeneroArea.csv"
val dataSource1 = new File(path2DataFile2).readCsv[List, (String, Int, Int, Int, Int, Int, Int)](rfc)
dataSource1.foreach(println _)
val path2DataFile2 = "EstudioPoblacionLojaGeneroArea.csv"
val dataSource1 = new File(path2DataFile2).readCsv[List, (String, Int, Int, Int, Int, Int, Int)](rfc)
dataSource1.foreach(println _)

Kantan

Leer filas como tuplas e indicar si existe cabecera de columnas

val path2DataFile2 = "EstudioPoblacionLojaGeneroArea.csv" 
val dataSource1 = new File(path2DataFile2).readCsv[List, (String, Int, Int, Int, Int, Int, Int)](rfc.withHeader(true)) 
dataSource1.foreach(println _)

JUGADOR;CLUB;NACIONALIDAD;GOLES;AUTOGOL
AGUIRRE SOTO RODRIGO SEBASTIAN;L.D.U.QUITO;URUGUAYA;12;No
ALEMAN ALEGRIA CHRISTIAN FERNANDO;BARCELONA S.C.;ECUATORIANA;6;No
ALVARADO CARRIEL ALEXANDER ANTONIO;S.D.AUCAS;ECUATORIANA;1;No
ALVEZ SAGAR JONATAN DANIEL;BARCELONA S.C.;URUGUAYA;2;No
AMARILLA LENCINA LUIS ANTONIO;U.CATOLICA;PARAGUAYA;16;No
AMIEVA JUAN MARTIN;MUSHUC RUNA S.C.;ARGENTINA;4;No
ANANGONO LEON JUAN LUIS;L.D.U.QUITO;ECUATORIANA;3;No
ANGULO ARROYO DANIEL PATRICIO;C.S.EMELEC;ECUATORIANA;5;No
ANGULO MEDINA JULIO EDUARDO;L.D.U.QUITO;ECUATORIANA;1;No
ANGULO TENORIO BRYAN DENNIS;C.S.EMELEC;ECUATORIANA;5;No

Kantan

Separador diferente a la coma

val path2DataFile2 = "Goleadores_LigaPro_2019.csv"
val dataSource2 = new File(path2DataFile2).readCsv[List, (String, String, String, Int, String)](rfc.withHeader(true).withCellSeparator(';'))
dataSource2.foreach(println _)

Collector

Es una función que se aplica en colecciones. 

val values = List(1, "A", 2, "B")
values: List[Any] = List(1, A, 2, B)

Es una función que recibe una función parcial como parámetro y la aplica a todos los elementos de la colección. Su objetivo es crear una nueva colección que contenga únicamente aquellos elementos que cumplen con la función parcial.

Trabajo básico con datos

Seleccionar filas válidas

val path2DataFile2 = "Goleadores_LigaPro_2019.csv"
val dataSource2 = new File(path2DataFile2).readCsv[List, (String, String, String, Int,
String)](rfc.withHeader(true).withCellSeparator(';')) 

val rows = dataSource2.collect({ case Right(goleador) => goleador })

Es posible que algunos datos no se asignen el formato especificado y sean procesados con Left.

- Esos datos se podría dejar de lado mientras se soluciona

- Se debería trabajar únicamente con los datos Rigth

Trabajo básico con datos

Seleccionar filas válidas

val path2DataFile2 = "Goleadores_LigaPro_2019.csv"
val dataSource2 = new File(path2DataFile2).readCsv[List, (String, String, String, Int,
String)](rfc.withHeader(true).withCellSeparator(';')) 

val rows = dataSource2.collect({ case Right(goleador) => goleador })

Trabajo básico con datos

Contar las filas

val path2DataFile2 = "Goleadores_LigaPro_2019.csv"
val dataSource2 = new File(path2DataFile2).readCsv[List, (String, String, String,
Int, String)](rfc.withHeader(true).withCellSeparator(';')) 
val rows = dataSource2.collect({ case Right(goleador) => goleador })
println(rows.length)

Trabajo básico con datos

Estadísticas básicas sobre enteros

val golesAvg = rows.map(_._4).sum / rows.length.toDouble
val numMayorGoles = rows.map(_._4).max
  • ¿Cuál es el promedio de los goles marcados?
  • ¿Cuál es el número mayor de goles?

Trabajo básico con datos

¿Cuál es el número mayor de goles que han marcado por equipo? Inicie con un equipo.

Otras operaciones

Case class

Clases y objetos

Breve revisión

  • POO como paradigma de programación
  • Todo se representa como clases y relaciones

 

Clases y objetos

Clase y objetos java verbosos

Necesitamos escribir mucho código

Disponible a partir de Java 16

¿Scala tiene algo parecido?

Clase class

Descripción

Representación simple e inmutable de datos

Compilador crea varios métodos

  • Métodos de acceso para cada atributo
  • Método apply para crear nuevos objetos
  • copy, equals, hashcode, toString
case class CovidProvinceStats(provinceId: String, deaths: Int, confirmedCases: Int)

Clase class

Instancias

No se usa el operador new. 

No se necesitan métodos de acceso

val lojaStats = CovidProvinceStats("11", 1400, 2909)
case class CovidProviceStats(provinceId: String, deaths: Int, confirmedCases: Int)
lojaStats.provinceId
val lojaStats = CovidProvinceStats("11", 1400, 2909)

Clase class

Métodos utiles

copy

Tupled

val elOroStats = lojaStats.copy("12", 2400, 30456)
val tuple = ("17", 12000, 3455)
val quitoStats = (CovidProceStats.apply _).tupled(tuple)

Clase class

Representación

case Class CovidProvinceStats(pronviceId: String, deaths: Int, confirmedCases: Int)

Clase class

Representación

case Class CovidProvinceStats(pronviceId: String, deaths: Int, confirmedCases: Int)

Case class y Katan

CSV a case class

val golesAvg = rows.map(_._4).sum / rows.length.toDouble

Para tener un acceso más específico a las filas se puede usar case class

val golesAvg = rows.map(_.goles).sum / rows.length.toDouble

Además las tuplas tienen un límite de 22 elementos Tuple22

Case class y Katan

CSV a case class

scalaVersion := "2.13.7" 
// Core library
libraryDependencies += "com.nrinaudo" %% "kantan.csv" % "0.6.1"
libraryDependencies += "com.nrinaudo" %% “kantan.csv-generic" % "0.6.1"
case class Ciudades(
   Provincia: String,
   Canton: String,
   Parroquia: String,
   Femenino: Int,
   Masculino: Int
)
val dataSource = new File(path2DataFile).readCsv[List, Ciudades](rfc.withHeader)
val values = dataSource.collect({ case Right(ciudades) => ciudades })

permite mapear automáticamente un archivo CSV a una case class

Case class y Katan

Práctica

Usando los datos de los goleadores del copa ecuador 2019 crear una case clase que represente los datos y use kantan para leer los datos y representarlos como una lista de objetos (case class)

Case class y Katan

Definición de case class de movies

case class Movies(
                   adult: Boolean,
                   belongs_to_collection: String,
                   budget: Int,
                   ....
                   )
                   

Case class y Katan

Definición de object

object LecturaDatosProyectoIntegradorV3 extends App {
  val path2DataFile2 = "data/pi_movies_small.csv"

  // Configurar lectura del CSV con delimitador ';'
  val dataSource2 = new File(path2DataFile2)
    .readCsv[List, Movies2](rfc.withHeader.withCellSeparator(';'))

  // Filtrar filas válidas
  val rows = dataSource2.collect {
    case Right(movie) => movie
  }
  
 }
                   

Proyecto Integrador o Bimestral

Proyecto Integrador o bimestral

Entrega 1 - 9 de enero de 2025

  • Repositorio en GitHub (incluir todos los integrantes + docentes)
  • Tablas de datos (nombre de columna, tipo, propósito y observaciones) - Readme.md 
  • Análisis de datos en columnas numéricas (estadísticas básicas)
  • Análisis de datos en columnas tipo texto (algunas col. - distribución de frecuencia). OJO: no considerar columnas en formato JSON
  • Consultar sobre librería play-json (trabajo json en scala) y hacer:
    • Usar cualquier JSON pequeño para aprender play-json
    • Usar en algunas columnas JSON para obtener datos.