Análisis exploratorios de datos (EDA) a través de estadísticas básicas

Programación funcional y reactiva - Computación

Profesor: Ing. Santiago Quiñones

Docente Investigador

Departamento de Ingeniería Civil

Contenidos

Datos

Datos

Definición

Los "datos" son representaciones simbólicas de hechos, conceptos o instrucciones que se recopilan, procesan y analizan para extraer información valiosa. Pueden ser cualitativos o cuantitativos, y su importancia radica en su capacidad para respaldar decisiones, generar conocimiento y facilitar innovaciones en diversos campos.

Importancia de los datos

  • Toma de decisiones informadas
  • Innovación y desarrollo
  • Optimización de recursos
  • Predicción y prevención

Debate en grupos

  • Mejora en la educación personalizada
  • Avances en investigación científica
  • Eficiencia en servicios públicos
  • Desarrollo económico

Grupos A: Beneficios de los datos

  • Privacidad y seguridad de datos personales
  • Brecha digital y acceso desigual
  • Dependencia tecnológica
  • Sesgos en los algoritmos

Grupos B: Desafios y preocupaciones

Análisis exploratorio de datos (EDA)

Análisis exploratorio de datos (EDA)

Análisis exploratorio de datos (EDA)

Uso de análisis exploratorio

  • Conocer la estructura y distribución de nuestros datos
  • Encontrar las relaciones entre las variables explicatorias
  • Explicar la relación que tienen las variables explicatorias con la variable respuesta
  • Encontrar posibles errores, puntos extremos y anomalías de los datos
  • Refinar nuestra hipótesis, o genera nuevas preguntas sobre los datos que tenemos

Análisis exploratorio de datos (EDA)

 

Un ejemplo integrado sería: Imagina que analizas datos de una aplicación de fitness:

  • Primero ves la estructura: datos de usuarios (edad, género, peso, altura)
  • Encuentras relaciones: usuarios más jóvenes tienden a ejercitarse más tiempo
  • Relacionas con la variable respuesta: más tiempo de ejercicio = más pérdida de peso
  • Identificas anomalías: registros imposibles como alturas de 3 metros
  • Generas nuevas preguntas: ¿Por qué los usuarios abandonan después de 3 meses?

Estadísticas descriptivas

La moda: el valor más frecuente que toma una variable

Estadísticas descriptivas

La moda: el valor más frecuente que toma una variable

import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
import java.io.File

case class Goleador(
  jugador: String,
  club: String,
  nacionalidad: String,
  goles: Int,
  autogol: String
)

object Ejemplo3_Goleadores_Moda extends App {
  // Leer el archivo CSV
  val path2DataFile2 = "data/Goleadores_LigaPro_2019.csv"
  
  val dataSource2 = new File(path2DataFile2)
    .readCsv[List, Goleador](rfc.withHeader.withCellSeparator(';'))

  // Obtener datos válidos
  val rows_ok = dataSource2.collect {
    case Right(goleador) => goleador
  }

  // Calcular la moda usando programación funcional
  val moda = rows_ok
    .map(_.goles)                    // Obtener solo los goles
    .groupBy(identity)               // Agrupar valores idénticos
    .map { case (gol, lista) =>      // Transformar a (gol, frecuencia)
      (gol, lista.length)
    }
    .maxBy(_._2)                     // Obtener el de mayor frecuencia

  // Mostrar resultados
  println(s"La moda es: ${moda._1} goles (aparece ${moda._2} veces)")
  
  // Mostrar jugadores que tienen esa cantidad de goles
  println("\nJugadores con esta cantidad de goles:")
  rows_ok
    .filter(_.goles == moda._1)
    .foreach(j => println(s"${j.jugador} (${j.club})"))
}
// Si tenemos estos goles: [2, 2, 3, 2, 3, 4]
Map(
    2 -> List(2, 2, 2),    // tres jugadores con 2 goles
    3 -> List(3, 3),       // dos jugadores con 3 goles
    4 -> List(4)           // un jugador con 4 goles
)

Estadísticas descriptivas

La media: "el promedio"

Estadísticas descriptivas

La media: "el promedio"

package b2s12.jugadores

import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
import java.io.File

import models.Goleador

object EDA2_promedio extends App {
  // Leer el archivo CSV
  val path2DataFile2 = "data/Goleadores_LigaPro_2019.csv"

  val dataSource2 = new File(path2DataFile2)
    .readCsv[List, Goleador](rfc.withHeader.withCellSeparator(';'))

  // Obtener datos válidos
  val rows_ok = dataSource2.collect {
    case Right(goleador) => goleador
  }

  // Cálculo del promedio usando métodos de colección
  val promedio = rows_ok.map(_.goles).sum.toDouble / rows_ok.length

  // Mostrar resultados
  println(s"El promedio de goles es: $promedio")

  // Estadísticas adicionales
  val totalJugadores = rows_ok.length
  val jugadoresArribaPromedio = rows_ok.count(_.goles > promedio)
  val jugadoresAbajoPromedio = rows_ok.count(_.goles < promedio)

  println("\nEstadísticas:")
  println(s"Total de jugadores: $totalJugadores")
  println(s"Jugadores arriba del promedio: $jugadoresArribaPromedio")
  println(s"Jugadores abajo del promedio: $jugadoresAbajoPromedio")
}

Estadísticas descriptivas

El rango: el "tamaño" de los valores, la diferencia entre el valor más grande y el más pequeño

Estadísticas descriptivas

El rango: el "tamaño" de los valores, la diferencia entre el valor más grande y el más pequeño

// Calcular el rango usando programación funcional
  val goles = rows_ok.map(_.goles)

  val maximoGoles = goles.max
  val minimoGoles = goles.min
  val rango = maximoGoles - minimoGoles

  // Mostrar resultados
  println(s"Rango de goles: $rango")
  println(s"Valor máximo: $maximoGoles goles")
  println(s"Valor mínimo: $minimoGoles goles")

Estadísticas descriptivas

Desviación estándar: cuantifica la dispersión del conjunto de datos numéricos

Estadísticas descriptivas

Desviación estándar: cuantifica la dispersión del conjunto de datos numéricos

// Calcular promedio (necesario para la desviación)
  val promedio = goles.sum.toDouble / goles.length

  // Calcular desviación estándar
  val desviacionEstandar = sqrt(
    goles.map(gol => math.pow(gol - promedio, 2))  // Diferencia al cuadrado
      .sum / goles.length                        // Promedio de las diferencias
  )

Estadísticas descriptivas

Correlación: Ayuda a entender entre las dos variables numéricas. Mide la fuerza  y dirección entre dos variables numéricas

El valor va de -1 a +1:

  • +1: Correlación positiva perfecta (cuando una aumenta, la otra aumenta)
  • 0: No hay correlación (no hay relación)
  • -1: Correlación negativa perfecta (cuando una aumenta, la otra disminuye)

Estadísticas descriptivas

Correlación: Ayuda a entender entre las dos variables numéricas. Mide la fuerza  y dirección entre dos variables numéricas

package b2s12.jugadores

import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
import java.io.File
import scala.math.{sqrt, pow}

// Case class extendido con partidos jugados
case class GoleadorExtendido(
                              jugador: String,
                              club: String,
                              nacionalidad: String,
                              goles: Int,
                              autogol: String,
                              partidos: Int  // Nuevo campo
                            )

object EDA5_correlacion extends App{
  // Función para calcular correlación de Pearson
  def calcularCorrelacion(x: List[Double], y: List[Double]): Double = {
    require(x.length == y.length && x.nonEmpty, "Las listas deben tener el mismo tamaño y no estar vacías")

    val n = x.length
    val promedioX = x.sum / n
    val promedioY = y.sum / n

    val numerador = x.zip(y).map { case (xi, yi) =>
      (xi - promedioX) * (yi - promedioY)
    }.sum

    val denominadorX = sqrt(x.map(xi => pow(xi - promedioX, 2)).sum)
    val denominadorY = sqrt(y.map(yi => pow(yi - promedioY, 2)).sum)

    numerador / (denominadorX * denominadorY)
  }

  // Simulamos algunos datos de ejemplo (en un caso real, estos vendrían del CSV)
  val datos = List(
    GoleadorExtendido("Jugador1", "Club A", "ECU", 10, "", 20),
    GoleadorExtendido("Jugador2", "Club B", "ARG", 15, "", 25),
    GoleadorExtendido("Jugador3", "Club C", "ECU", 8, "", 18),
    GoleadorExtendido("Jugador4", "Club D", "COL", 20, "", 30),
    GoleadorExtendido("Jugador5", "Club E", "ECU", 12, "", 22)
  )

  // Extraer listas de goles y partidos
  val goles = datos.map(_.goles.toDouble)
  val partidos = datos.map(_.partidos.toDouble)

  // Calcular correlación
  val correlacion = calcularCorrelacion(goles, partidos)

  // Mostrar resultados
  println(f"Correlación entre goles y partidos jugados: $correlacion%.4f")

  // Interpretación de la correlación
  val interpretacion = correlacion match {
    case r if r > 0.7 => "Correlación positiva fuerte"
    case r if r > 0.3 => "Correlación positiva moderada"
    case r if r > -0.3 => "Correlación débil o nula"
    case r if r > -0.7 => "Correlación negativa moderada"
    case _ => "Correlación negativa fuerte"
  }

  println(s"Interpretación: $interpretacion")

  // Mostrar datos para visualización
  println("\nDatos:")
  println("Jugador          Goles  Partidos  Goles/Partido")
  println("-" * 45)
  datos.foreach { j =>
    val golesPartido = j.goles.toDouble / j.partidos
    println(f"${j.jugador}%-15s ${j.goles}%6d ${j.partidos}%9d ${golesPartido}%13.2f")
  }

}

Limpieza de datos

Proyecto Integrador o bimestral

Comentarios

  • Es necesario realizar la limpieza de datos:
    • Cadenas vacías
  • Algunas columnas necesitan análisis cuidadoso, ejemplo:
    • Separador no definido: genres, cast (nombres)
    • Dato estructurado: release_date (fecha)

 

Dato estructurao

Date

  • Columna release_date

    • Formato: yyyy-MM-dd

    • No existe en todas las filas

 

import java.time.LocalDate
import java.time.format.DateTimeFormatter

// Procesar las fechas de manera segura
  val releaseDateList = validRows
    .flatMap(_.release_date) // extrae los valores de los Some y elimina los None
    .filter(_.nonEmpty) // Elimina strings vacíos
    .map { dateStr =>
      try {
        Some(LocalDate.parse(dateStr, dateFormatter))
      } catch {
        case _: Exception => None
      }
    }
    .flatten // Elimina los None resultantes de fechas inválidas

JavaScript Object Notation (JSON)

Aproximación

  • Un formato para representar información basado en clave valor (key/value)
  • Popular dentro de las aplicaciones Web
    • Características:
      • Las claves se encierran entre comillas dobles "
  • Estructura:
    • Objetos
      • Propiedades
    • Arreglos de: Objetos, valores, arreglos.

JavaScript Object Notation (JSON)

Objetos

JSON

Arreglos

JSON

Herramientas

Herramientas

{
  "nombre": "Cristina Guaman",
  "edad": 30,
  "esProfesor": true,
  "direccion": {
    "ciudad": "Loja",
    "pais": "Ecuador"
  },
  "hobbies": ["leer", "cocinar", "viajar"],
  "certificados": null
}

JSON

Movies DataSet

¿Cuántas columnas tipo JSON ha identificado? 

JSON

Movies DataSet

JSON

Scala

• Existen varias librerías para trabajar con JSON y Scala

• Una de ellas se denomina Play-JSON

• Información: https://www.playframework.com/documentation/2.8.x/ ScalaJson

JSON

Ejemplo base



import play.api.libs.json._

// Case class simple para representar una persona
case class Persona(nombre: String, edad: Int, email: Option[String])

object json_simple extends App {
  // JSON de ejemplo como String
  val jsonString = """
  {
    "nombre": "Ana García",
    "edad": 28,
    "email": "ana@email.com"
  }
  """

  // Formato implícito para convertir entre JSON y el case class
  // Le dice a Play JSON cómo convertir entre JSON y el case class Persona
  implicit val formatoPersona = Json.format[Persona]

  // Parseamos el JSON string a un objeto Persona
  val resultado = Json.parse(jsonString).as[Persona]

  // Mostramos el resultado
  println(s"Nombre: ${resultado.nombre}")
  println(s"Edad: ${resultado.edad}")
  println(s"Email: ${resultado.email.getOrElse("No disponible")}")

  
}

JSON

JSON con dos registros

package b2s12.limpieza

import play.api.libs.json._

case class Persona2(nombre: String, edad: Int, email: Option[String])

object json_dos_personas extends App{
  // JSON de ejemplo como String con array de personas
  val jsonString = """
  {
    "personas": [
      {
        "nombre": "Ana García",
        "edad": 28,
        "email": "ana@email.com"
      },
      {
        "nombre": "Carlos López",
        "edad": 35,
        "email": null
      }
    ]
  }
  """

  implicit val formatoPersona = Json.format[Persona2]

  // Parseamos el JSON string
  // Convierte el string JSON en un objeto JSON que podemos manipular
  val json = Json.parse(jsonString)

  // Obtenemos el array de personas (navega al campo "personas" del JSON)
  val personas = (json \ "personas").as[List[Persona2]]

  // Mostramos los resultados
  personas.foreach { persona =>
    println(s"\nDatos de persona:")
    println(s"Nombre: ${persona.nombre}")
    println(s"Edad: ${persona.edad}")
    println(s"Email: ${persona.email.getOrElse("No disponible")}")
  }
}

JSON

JSON con problemas

package b2s12.limpieza


import play.api.libs.json._

case class Crew(
                 name: String,
                 department: String,
                 job: String,
                 profile_path: Option[String]
               )

object json_mal_formado extends App {
  // JSON mal estructurado como String
  val jsonSucio = """
  {
    'crews': [
      {
        'name': 'John Smith',
        'department': 'Directing',
        'job': 'Director',
        'profile_path': None
      },
      {
        'name': 'Mary Johnson\\',
        'department': 'Production',
        'job': 'Producer',
        'profile_path': '\\path\\to\\profile'
      },
      {
        'name': 'Peter Parker',
        'department': 'Camera',
        'job': 'Cinematographer',
        'profile_path': None,
        'active': True
      }
    ]
  }
  """

  // Función para limpiar el JSON
  def cleanCrewJson(crewJson: String): String = {
    crewJson
      .trim
      .replaceAll("'", "\"") // Reemplazar comillas simples por dobles
      .replaceAll("None", "null") // Reemplazar None por null
      .replaceAll("True", "true") // Normalizar booleanos
      .replaceAll("False", "false")
      .replaceAll("""\\""", "") // Eliminar escapes innecesarios
  }

  implicit val crewFormat = Json.format[Crew]

  // Limpiamos y parseamos el JSON
  val jsonLimpio = cleanCrewJson(jsonSucio)
  val json = Json.parse(jsonLimpio)

  // Extraemos la lista de crews
  val crews = (json \ "crews").as[List[Crew]]

  // Mostramos los resultados
  println("Crews procesados:")
  crews.foreach { crew =>
    println("\nMiembro del equipo:")
    println(s"Nombre: ${crew.name}")
    println(s"Departamento: ${crew.department}")
    println(s"Trabajo: ${crew.job}")
    println(s"Perfil: ${crew.profile_path.getOrElse("No disponible")}")
  }

  // Mostramos el JSON limpio
  println("\nJSON limpio generado:")
  println(Json.prettyPrint(Json.toJson(Map("crews" -> crews))))
}

B2S12 EDA, Estadística Descriptiva

By Santiago Quiñones Cuenca

B2S12 EDA, Estadística Descriptiva

Introducción al Análisis exploratorio de datos (EDA) a través de estadísticas básicas.

  • 180