Procesamiento de Datos CSV y TSV

Manejo profesional de datos estructurados con herramientas de línea de comandos

Módulo 5 ⏱️ 35-40 min 📊 Datos 🔧 Procesamiento 📈 Intermedio

Introducción a CSV y TSV

CSV (Comma-Separated Values) y TSV (Tab-Separated Values) son formatos de archivo ampliamente utilizados para almacenar datos tabulares. Estos formatos son fundamentales en el análisis de datos, intercambio de información entre sistemas y procesamiento automatizado.

Comparación de Formatos

CSV (Comma-Separated Values)
  • Separador: Coma (,)
  • Ventajas: Estándar amplio, compatible con Excel
  • Desventajas: Problemas con comas en los datos
  • Uso: Exportación de bases de datos, spreadsheets
TSV (Tab-Separated Values)
  • Separador: Tabulación (\t)
  • Ventajas: Menos problemas con datos, más limpio
  • Desventajas: Menos común que CSV
  • Uso: Datos científicos, logs estructurados
Ejemplo CSV: nombre,edad,ciudad,salario Juan Pérez,28,Madrid,45000 María González,34,Barcelona,52000 Carlos Rodríguez,41,Valencia,48000
Ejemplo TSV: nombre edad ciudad salario Juan Pérez 28 Madrid 45000 María González 34 Barcelona 52000 Carlos Rodríguez 41 Valencia 48000
¿Por qué dominar CSV/TSV en la línea de comandos?
  • Velocidad: Procesamiento mucho más rápido que GUI
  • Automatización: Integración en scripts y pipelines
  • Escalabilidad: Manejo de archivos de cualquier tamaño
  • Flexibilidad: Combinación de herramientas potentes
  • Reproducibilidad: Comandos documentables y repetibles

Herramientas Básicas para CSV/TSV

Linux ofrece un conjunto robusto de herramientas para el procesamiento de datos estructurados. Cada herramienta tiene sus fortalezas específicas.

cut

Extracción rápida de columnas específicas

awk

Procesamiento avanzado y cálculos

sort

Ordenamiento por múltiples criterios

uniq

Eliminación de duplicados y conteo

join

Combinación de archivos relacionales

sed

Transformación y limpieza de datos

Operaciones Básicas

# Crear archivo de datos de ejemplo
$ cat > empleados.csv << 'EOF'
nombre,edad,departamento,salario,ciudad
Juan Pérez,28,IT,45000,Madrid
María González,34,Ventas,52000,Barcelona
Carlos Rodríguez,41,IT,48000,Valencia
Ana López,29,Marketing,38000,Madrid
Pedro Martínez,35,Ventas,49000,Sevilla
Elena García,26,IT,42000,Barcelona
EOF

# Ver primeras líneas
$ head -3 empleados.csv
nombre,edad,departamento,salario,ciudad
Juan Pérez,28,IT,45000,Madrid
María González,34,Ventas,52000,Barcelona

# Contar líneas (registros + header)
$ wc -l empleados.csv
7 empleados.csv

# Ver estructura de columnas
$ head -1 empleados.csv | tr ',' '\n' | nl
     1	nombre
     2	edad
     3	departamento
     4	salario
     5	ciudad

Extracción y Selección de Datos

La extracción eficiente de datos específicos es fundamental para el análisis. Aprenderemos técnicas avanzadas para seleccionar exactamente lo que necesitamos.

Selección de Columnas

Técnicas de selección Copiar
# Extraer columnas específicas con cut
$ cut -d',' -f1,3,4 empleados.csv
nombre,departamento,salario
Juan Pérez,IT,45000
María González,Ventas,52000
Carlos Rodríguez,IT,48000
Ana López,Marketing,38000
Pedro Martínez,Ventas,49000
Elena García,IT,42000

# Extraer rango de columnas
$ cut -d',' -f2-4 empleados.csv
edad,departamento,salario
28,IT,45000
34,Ventas,52000
41,IT,48000

# Selección con awk (más flexible)
$ awk -F',' '{print $1, $4}' empleados.csv
nombre salario
Juan Pérez 45000
María González 52000
Carlos Rodríguez 48000

# Selección con condiciones
$ awk -F',' 'NR>1 && $2 > 30 {print $1, $2, $3}' empleados.csv
María González 34 Ventas
Carlos Rodríguez 41 IT
Pedro Martínez 35 Ventas

# Reordenar columnas
$ awk -F',' '{print $4, $1, $3}' empleados.csv | head -3
salario nombre departamento
45000 Juan Pérez IT
52000 María González Ventas

Filtrado de Filas

# Filtrar por departamento
$ awk -F',' '$3 == "IT"' empleados.csv
Juan Pérez,28,IT,45000,Madrid
Carlos Rodríguez,41,IT,48000,Valencia
Elena García,26,IT,42000,Barcelona

# Filtrar por rango de salarios
$ awk -F',' 'NR==1 || ($4 >= 45000 && $4 <= 50000)' empleados.csv
nombre,edad,departamento,salario,ciudad
Juan Pérez,28,IT,45000,Madrid
Carlos Rodríguez,41,IT,48000,Valencia
Pedro Martínez,35,Ventas,49000,Sevilla

# Filtrar por múltiples condiciones
$ awk -F',' 'NR==1 || ($3=="IT" && $2<30)' empleados.csv
nombre,edad,departamento,salario,ciudad
Juan Pérez,28,IT,45000,Madrid
Elena García,26,IT,42000,Barcelona

# Usar grep para filtros simples
$ grep "Madrid\|Barcelona" empleados.csv
Juan Pérez,28,IT,45000,Madrid
María González,34,Ventas,52000,Barcelona
Ana López,29,Marketing,38000,Madrid
Elena García,26,IT,42000,Barcelona
Cuidado con las Comas en los Datos

Si tus datos CSV contienen comas dentro de los campos (ej: "García, Juan"), necesitarás herramientas más sofisticadas que manejen campos entre comillas. En estos casos, considera usar herramientas como csvkit o procesar con scripts de Python/Perl.

Análisis y Cálculos

El verdadero poder del procesamiento de CSV/TSV surge cuando realizamos cálculos y análisis estadísticos directamente desde la línea de comandos.

Estadísticas Básicas

Cálculos estadísticos Copiar
# Suma de salarios totales
$ awk -F',' 'NR>1 {sum+=$4} END {print "Suma total salarios:", sum}' empleados.csv
Suma total salarios: 274000

# Promedio de salarios
$ awk -F',' 'NR>1 {sum+=$4; count++} END {print "Salario promedio:", sum/count}' empleados.csv
Salario promedio: 45666.7

# Salario mínimo y máximo
$ awk -F',' 'NR>1 {
    if(min=="" || $4max) max=$4
} END {print "Min:", min, "Max:", max}' empleados.csv
Min: 38000 Max: 52000

# Estadísticas por departamento
$ awk -F',' 'NR>1 {
    dept[$3]++;
    salario_dept[$3]+=$4
} END {
    for(d in dept) {
        printf "%-12s: %d empleados, salario promedio: %.0f\n", 
               d, dept[d], salario_dept[d]/dept[d]
    }
}' empleados.csv
IT          : 3 empleados, salario promedio: 45000
Marketing   : 1 empleados, salario promedio: 38000
Ventas      : 2 empleados, salario promedio: 50500

# Distribución por edades
$ awk -F',' 'NR>1 {
    if($2<30) rango["20-29"]++
    else if($2<40) rango["30-39"]++
    else rango["40+"]++
} END {
    for(r in rango) print r":", rango[r] " empleados"
}' empleados.csv
20-29: 3 empleados
30-39: 2 empleados
40+: 1 empleados

Agrupación y Agregación

# Contar empleados por ciudad
$ awk -F',' 'NR>1 {ciudad[$5]++} END {for(c in ciudad) print c, ciudad[c]}' empleados.csv | sort -k2 -nr
Madrid 2
Barcelona 2
Valencia 1
Sevilla 1

# Salary budget por departamento
$ awk -F',' 'NR>1 {budget[$3]+=$4} END {for(d in budget) printf "%-12s: €%d\n", d, budget[d]}' empleados.csv
IT          : €135000
Marketing   : €38000
Ventas      : €101000

# Top 3 salarios más altos
$ awk -F',' 'NR>1 {print $4, $1}' empleados.csv | sort -nr | head -3
52000 María González
49000 Pedro Martínez
48000 Carlos Rodríguez

# Empleados con salario por encima del promedio
$ awk -F',' '
NR>1 {suma+=$4; empleados[NR]=$0}
END {
    promedio = suma/(NR-1)
    printf "Salario promedio: %.0f\n", promedio
    print "Empleados por encima del promedio:"
    for(i=2; i<=NR; i++) {
        split(empleados[i], campos, ",")
        if(campos[4] > promedio) {
            print campos[1], campos[4]
        }
    }
}' empleados.csv
Salario promedio: 45667
Empleados por encima del promedio:
María González 52000
Carlos Rodríguez 48000
Pedro Martínez 49000

Ordenamiento y Clasificación

El comando sort es extremadamente potente para ordenar datos CSV/TSV por múltiples criterios y tipos de datos.

Ordenamiento Avanzado

Técnicas de ordenamiento Copiar
# Ordenar por salario (numérico, descendente)
$ (head -1 empleados.csv; tail -n +2 empleados.csv | sort -t',' -k4 -nr)
nombre,edad,departamento,salario,ciudad
María González,34,Ventas,52000,Barcelona
Pedro Martínez,35,Ventas,49000,Sevilla
Carlos Rodríguez,41,IT,48000,Valencia
Juan Pérez,28,IT,45000,Madrid
Elena García,26,IT,42000,Barcelona
Ana López,29,Marketing,38000,Madrid

# Ordenar por departamento, luego por salario
$ (head -1 empleados.csv; tail -n +2 empleados.csv | sort -t',' -k3,3 -k4,4nr)
nombre,edad,departamento,salario,ciudad
Carlos Rodríguez,41,IT,48000,Valencia
Juan Pérez,28,IT,45000,Madrid
Elena García,26,IT,42000,Barcelona
Ana López,29,Marketing,38000,Madrid
María González,34,Ventas,52000,Barcelona
Pedro Martínez,35,Ventas,49000,Sevilla

# Ordenar por edad (preservando header)
$ (head -1 empleados.csv; tail -n +2 empleados.csv | sort -t',' -k2 -n)
nombre,edad,departamento,salario,ciudad
Elena García,26,IT,42000,Barcelona
Juan Pérez,28,IT,45000,Madrid
Ana López,29,Marketing,38000,Madrid
María González,34,Ventas,52000,Barcelona
Pedro Martínez,35,Ventas,49000,Sevilla
Carlos Rodríguez,41,IT,48000,Valencia

# Ordenar por ciudad, luego por nombre
$ (head -1 empleados.csv; tail -n +2 empleados.csv | sort -t',' -k5,5 -k1,1)
nombre,edad,departamento,salario,ciudad
Elena García,26,IT,42000,Barcelona
María González,34,Ventas,52000,Barcelona
Ana López,29,Marketing,38000,Madrid
Juan Pérez,28,IT,45000,Madrid
Pedro Martínez,35,Ventas,49000,Sevilla
Carlos Rodríguez,41,IT,48000,Valencia
Claves de Ordenamiento de sort
  • -t',' - Especifica el delimitador (coma para CSV)
  • -k3 - Ordena por la columna 3
  • -n - Ordenamiento numérico
  • -r - Orden reverso (descendente)
  • -k3,3 - Solo la columna 3 (no hasta el final)
  • -k1,1 -k2,2nr - Múltiples claves de ordenamiento

Combinación y Join de Datos

Frecuentemente necesitamos combinar datos de múltiples archivos CSV. El comando join y técnicas con awk nos permiten realizar operaciones relacionales complejas.

Preparación de Datos

# Crear archivo de departamentos
$ cat > departamentos.csv << 'EOF'
departamento,presupuesto,jefe
IT,200000,Carlos Silva
Ventas,150000,Ana Ruiz
Marketing,100000,Pedro López
EOF

# Crear archivo de proyectos
$ cat > proyectos.csv << 'EOF'
proyecto,empleado,horas,fecha_inicio
Web App,Juan Pérez,120,2024-01-15
CRM,María González,80,2024-01-20
API REST,Carlos Rodríguez,100,2024-02-01
Campaign,Ana López,60,2024-02-10
E-commerce,Pedro Martínez,150,2024-02-15
EOF

Join con sort y join

Operaciones join Copiar
# Preparar archivos para join (deben estar ordenados)
$ tail -n +2 empleados.csv | cut -d',' -f3,1,4 | sort > emp_sorted.csv
$ tail -n +2 departamentos.csv | sort > dept_sorted.csv

$ cat emp_sorted.csv
IT,Carlos Rodríguez,48000
IT,Elena García,42000
IT,Juan Pérez,45000
Marketing,Ana López,38000
Ventas,María González,52000
Ventas,Pedro Martínez,49000

$ cat dept_sorted.csv
IT,200000,Carlos Silva
Marketing,100000,Pedro López
Ventas,150000,Ana Ruiz

# Join empleados con departamentos
$ join -t',' emp_sorted.csv dept_sorted.csv
IT,Carlos Rodríguez,48000,200000,Carlos Silva
IT,Elena García,42000,200000,Carlos Silva
IT,Juan Pérez,45000,200000,Carlos Silva
Marketing,Ana López,38000,100000,Pedro López
Ventas,María González,52000,150000,Ana Ruiz
Ventas,Pedro Martínez,49000,150000,Ana Ruiz

# Join con campos específicos y formato personalizado
$ join -t',' -o 1.2,1.3,2.2,2.3 emp_sorted.csv dept_sorted.csv
Carlos Rodríguez,48000,200000,Carlos Silva
Elena García,42000,200000,Carlos Silva
Juan Pérez,45000,200000,Carlos Silva
Ana López,38000,100000,Pedro López
María González,52000,150000,Ana Ruiz
Pedro Martínez,49000,150000,Ana Ruiz

Join con awk (más flexible)

Join avanzado con awk Copiar
# Script awk para join complejo
$ awk -F',' '
BEGIN { OFS="," }
# Leer archivo de departamentos primero
NR==FNR && NR>1 {
    dept_budget[$1] = $2
    dept_jefe[$1] = $3
    next
}
# Procesar empleados
NR>1 && FNR>1 {
    if($3 in dept_budget) {
        ratio = $4 / (dept_budget[$3] / 100)
        printf "%s,%s,%.1f%%,%s\n", $1, $3, ratio, dept_jefe[$3]
    }
}' departamentos.csv empleados.csv
Juan Pérez,IT,2.3%,Carlos Silva
María González,Ventas,3.5%,Ana Ruiz
Carlos Rodríguez,IT,2.4%,Carlos Silva
Ana López,Marketing,3.8%,Pedro López
Pedro Martínez,Ventas,3.3%,Ana Ruiz
Elena García,IT,2.1%,Carlos Silva

# Resumen de costos por departamento
$ awk -F',' '
NR==FNR && NR>1 { budget[$1] = $2; next }
NR>1 && FNR>1 { 
    dept_empleados[$3]++; 
    dept_salarios[$3] += $4 
}
END {
    print "Departamento,Empleados,Gasto_Salarios,Presupuesto,% Usado"
    for(d in dept_empleados) {
        if(d in budget) {
            pct = (dept_salarios[d] / budget[d]) * 100
            printf "%s,%d,%d,%s,%.1f%%\n", d, dept_empleados[d], dept_salarios[d], budget[d], pct
        }
    }
}' departamentos.csv empleados.csv
Departamento,Empleados,Gasto_Salarios,Presupuesto,% Usado
IT,3,135000,200000,67.5%
Marketing,1,38000,100000,38.0%
Ventas,2,101000,150000,67.3%

Limpieza y Transformación

Los datos del mundo real rara vez están perfectos. Necesitamos técnicas para limpiar, normalizar y transformar datos antes del análisis.

Problemas Comunes y Soluciones

Limpieza de datos Copiar
# Crear archivo con datos "sucios"
$ cat > datos_sucios.csv << 'EOF'
nombre,edad,salario,departamento
 Juan Pérez ,28,45.000,IT
María   González,34,"52,000",VENTAS
carlos rodriguez,41,48000 ,it
Ana López,29,  38000,Marketing  
Pedro Martínez,35,$49000,Ventas
EOF

# 1. Eliminar espacios en blanco extra
$ sed 's/^[ \t]*//g; s/[ \t]*$//g; s/[ \t]*,[ \t]*/,/g' datos_sucios.csv
nombre,edad,salario,departamento
Juan Pérez,28,45.000,IT
María   González,34,"52,000",VENTAS
carlos rodriguez,41,48000,it
Ana López,29,38000,Marketing
Pedro Martínez,35,$49000,Ventas

# 2. Normalizar departamentos (mayúsculas)
$ awk -F',' 'BEGIN{OFS=","} 
{
    # Limpiar espacios
    gsub(/^[ \t]+|[ \t]+$/, "", $4)
    # Convertir a mayúsculas
    $4 = toupper($4)
    print
}' datos_sucios.csv
nombre,edad,salario,departamento
 Juan Pérez ,28,45.000,IT
María   González,34,"52,000",VENTAS
carlos rodriguez,41,48000 ,IT
Ana López,29,  38000,MARKETING
Pedro Martínez,35,$49000,VENTAS

# 3. Limpiar salarios (quitar símbolos, normalizar)
$ awk -F',' 'BEGIN{OFS=","} 
NR==1 {print; next}
{
    # Limpiar nombre (espacios extra)
    gsub(/[ \t]+/, " ", $1)
    gsub(/^[ \t]+|[ \t]+$/, "", $1)
    
    # Limpiar salario
    gsub(/[$,".]/, "", $3)  # Quitar $, comas, puntos
    gsub(/^[ \t]+|[ \t]+$/, "", $3)  # Quitar espacios
    
    # Normalizar departamento
    gsub(/^[ \t]+|[ \t]+$/, "", $4)
    $4 = toupper($4)
    
    print
}' datos_sucios.csv
nombre,edad,salario,departamento
Juan Pérez,28,45000,IT
María González,34,52000,VENTAS
carlos rodriguez,41,48000,IT
Ana López,29,38000,MARKETING
Pedro Martínez,35,49000,VENTAS

# 4. Script completo de limpieza
$ cat > clean_csv.awk << 'EOF'
BEGIN { FS=OFS="," }
NR==1 { 
    # Procesar header
    for(i=1; i<=NF; i++) {
        gsub(/^[ \t]+|[ \t]+$/, "", $i)
        $i = tolower($i)
    }
    print
    next 
}
{
    # Limpiar nombre
    gsub(/^[ \t]+|[ \t]+$/, "", $1)
    gsub(/[ \t]+/, " ", $1)
    
    # Validar edad (solo números)
    if($2 !~ /^[0-9]+$/) $2 = "N/A"
    
    # Limpiar salario
    gsub(/[$,".]/, "", $3)
    gsub(/^[ \t]+|[ \t]+$/, "", $3)
    if($3 !~ /^[0-9]+$/) $3 = "0"
    
    # Normalizar departamento
    gsub(/^[ \t]+|[ \t]+$/, "", $4)
    $4 = toupper($4)
    
    print
}
EOF

$ awk -f clean_csv.awk datos_sucios.csv
nombre,edad,salario,departamento
Juan Pérez,28,45000,IT
María González,34,52000,VENTAS
carlos rodriguez,41,48000,IT
Ana López,29,38000,MARKETING
Pedro Martínez,35,49000,VENTAS

Detección de Datos Inconsistentes

# Detectar valores únicos por columna
$ awk -F',' 'NR>1 {dept[$4]++} END {print "Departamentos únicos:"; for(d in dept) print "-", d}' datos_sucios.csv
Departamentos únicos:
- IT
- VENTAS
- it
- Marketing  
- Ventas

# Detectar outliers en salarios
$ awk -F',' 'NR>1 {
    gsub(/[$,".]/, "", $3)
    if($3 > 0) {
        salarios[NR] = $3
        suma += $3
        count++
    }
} END {
    promedio = suma/count
    print "Salario promedio:", promedio
    print "Posibles outliers:"
    for(i in salarios) {
        if(salarios[i] > promedio*1.5 || salarios[i] < promedio*0.5) {
            print "  Línea", i-1": salario", salarios[i]
        }
    }
}' datos_sucios.csv

# Validar formato de campos
$ awk -F',' '
NR==1 {next}
{
    errors = 0
    # Validar nombre (no vacío, solo letras y espacios)
    if($1 == "" || $1 !~ /^[A-Za-z ]+$/) {
        print "Error línea " NR ": nombre inválido:", $1
        errors++
    }
    
    # Validar edad (número entre 18 y 65)
    if($2 !~ /^[0-9]+$/ || $2 < 18 || $2 > 65) {
        print "Error línea " NR ": edad inválida:", $2
        errors++
    }
    
    if(errors == 0) print "Línea " NR ": OK"
}' datos_sucios.csv

Pipeline Completo de Procesamiento

Integremos todo lo aprendido en un pipeline completo que demuestre el poder del procesamiento de CSV/TSV desde la línea de comandos.

Script integral de análisis CSV Copiar
#!/bin/bash
# csv_analyzer.sh - Analizador integral de archivos CSV

CSV_FILE="$1"
REPORT_DIR="reports"
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')

# Validar argumentos
if [[ $# -ne 1 || ! -f "$CSV_FILE" ]]; then
    echo "Uso: $0 archivo.csv"
    echo "El archivo debe existir"
    exit 1
fi

# Crear directorio de reportes
mkdir -p "$REPORT_DIR"

echo "🔍 Analizando archivo: $CSV_FILE"
echo "📊 Generando reportes en: $REPORT_DIR/"
echo

# 1. ANÁLISIS BÁSICO
echo "=== ANÁLISIS BÁSICO ==="
TOTAL_LINES=$(wc -l < "$CSV_FILE")
TOTAL_RECORDS=$((TOTAL_LINES - 1))
echo "📄 Total de líneas: $TOTAL_LINES"
echo "📋 Total de registros: $TOTAL_RECORDS"

# Detectar delimitador
COMMA_COUNT=$(head -1 "$CSV_FILE" | tr -cd ',' | wc -c)
TAB_COUNT=$(head -1 "$CSV_FILE" | tr -cd '\t' | wc -c)
if [[ $COMMA_COUNT -gt $TAB_COUNT ]]; then
    DELIMITER=","
    echo "🔗 Delimitador detectado: coma (CSV)"
else
    DELIMITER=$'\t'
    echo "🔗 Delimitador detectado: tabulación (TSV)"
fi

# Número de columnas
COLUMNS=$(head -1 "$CSV_FILE" | tr "$DELIMITER" '\n' | wc -l)
echo "📊 Número de columnas: $COLUMNS"
echo

# 2. ESTRUCTURA DE DATOS
echo "=== ESTRUCTURA DE DATOS ==="
echo "Columnas detectadas:"
head -1 "$CSV_FILE" | tr "$DELIMITER" '\n' | nl -w3 -s') '
echo

# 3. ANÁLISIS DE CALIDAD DE DATOS
echo "=== ANÁLISIS DE CALIDAD ==="
QUALITY_REPORT="$REPORT_DIR/quality_report_$TIMESTAMP.txt"

{
    echo "Reporte de Calidad de Datos - $(date)"
    echo "Archivo: $CSV_FILE"
    echo "======================================="
    echo
    
    # Detectar líneas vacías
    EMPTY_LINES=$(grep -c "^$" "$CSV_FILE")
    echo "Líneas vacías: $EMPTY_LINES"
    
    # Detectar líneas con diferente número de campos
    echo "Verificando consistencia de campos..."
    awk -F"$DELIMITER" -v expected="$COLUMNS" '
    NR==1 { next }
    NF != expected { 
        print "Línea " NR ": " NF " campos (esperado: " expected ")" 
        inconsistent++
    }
    END { 
        if(inconsistent > 0) print "Total líneas inconsistentes:", inconsistent
        else print "✅ Todas las líneas tienen el número correcto de campos"
    }' "$CSV_FILE"
    
    # Detectar valores faltantes
    echo
    echo "Análisis de valores faltantes por columna:"
    awk -F"$DELIMITER" '
    NR==1 { 
        for(i=1; i<=NF; i++) header[i] = $i
        next 
    }
    {
        for(i=1; i<=NF; i++) {
            if($i == "" || $i ~ /^[ \t]*$/) missing[i]++
            total[i]++
        }
    }
    END {
        for(i=1; i<=length(header); i++) {
            pct = (missing[i]/total[i])*100
            printf "%-20s: %d faltantes (%.1f%%)\n", header[i], missing[i], pct
        }
    }' "$CSV_FILE"
    
} > "$QUALITY_REPORT"

cat "$QUALITY_REPORT"
echo "📋 Reporte de calidad guardado en: $QUALITY_REPORT"
echo

# 4. ANÁLISIS ESTADÍSTICO
echo "=== ANÁLISIS ESTADÍSTICO ==="
STATS_REPORT="$REPORT_DIR/stats_report_$TIMESTAMP.txt"

{
    echo "Reporte Estadístico - $(date)"
    echo "Archivo: $CSV_FILE"
    echo "=============================="
    echo
    
    # Identificar columnas numéricas
    echo "Análisis de columnas numéricas:"
    awk -F"$DELIMITER" '
    NR==1 { 
        for(i=1; i<=NF; i++) header[i] = $i
        next 
    }
    {
        for(i=1; i<=NF; i++) {
            if($i ~ /^[0-9]+(\.[0-9]+)?$/) {
                numeric[i] = 1
                values[i][NR] = $i
                sum[i] += $i
                count[i]++
                if(min[i] == "" || $i < min[i]) min[i] = $i
                if(max[i] == "" || $i > max[i]) max[i] = $i
            }
        }
    }
    END {
        for(i=1; i<=length(header); i++) {
            if(numeric[i]) {
                avg = sum[i]/count[i]
                printf "\n%-20s:\n", header[i]
                printf "  Min: %g, Max: %g, Promedio: %.2f\n", min[i], max[i], avg
                printf "  Total: %g, Registros: %d\n", sum[i], count[i]
            }
        }
    }' "$CSV_FILE"
    
    echo
    echo "Análisis de columnas categóricas:"
    awk -F"$DELIMITER" '
    NR==1 { 
        for(i=1; i<=NF; i++) header[i] = $i
        next 
    }
    {
        for(i=1; i<=NF; i++) {
            if($i !~ /^[0-9]+(\.[0-9]+)?$/ && $i != "") {
                categorical[i][tolower($i)]++
                cat_total[i]++
            }
        }
    }
    END {
        for(i=1; i<=length(header); i++) {
            if(length(categorical[i]) > 0) {
                printf "\n%-20s: (%d valores únicos)\n", header[i], length(categorical[i])
                # Mostrar top 5 valores
                j = 0
                for(val in categorical[i]) {
                    if(j++ < 5) {
                        pct = (categorical[i][val]/cat_total[i])*100
                        printf "  %-15s: %d (%.1f%%)\n", val, categorical[i][val], pct
                    }
                }
                if(length(categorical[i]) > 5) {
                    printf "  ... y %d más\n", length(categorical[i])-5
                }
            }
        }
    }' "$CSV_FILE"
    
} > "$STATS_REPORT"

cat "$STATS_REPORT"
echo "📊 Reporte estadístico guardado en: $STATS_REPORT"
echo

# 5. GENERAR VISUALIZACIÓN BÁSICA (ASCII)
echo "=== VISUALIZACIÓN ASCII ==="
VIZ_REPORT="$REPORT_DIR/visualization_$TIMESTAMP.txt"

{
    echo "Visualización ASCII - $(date)"
    echo "============================="
    echo
    
    # Histograma simple de primera columna numérica
    awk -F"$DELIMITER" '
    NR==1 { next }
    $2 ~ /^[0-9]+$/ {
        ranges[int($2/10)*10]++
    }
    END {
        print "Distribución por rangos (columna 2):"
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for(range in ranges) {
            printf "%2d-%2d: ", range, range+9
            for(i=0; i "$VIZ_REPORT"

cat "$VIZ_REPORT"
echo "📈 Visualización guardada en: $VIZ_REPORT"
echo

# 6. REPORTE FINAL
FINAL_REPORT="$REPORT_DIR/final_report_$TIMESTAMP.html"
{
    cat << EOF




    Reporte de Análisis CSV - $(basename "$CSV_FILE")
    


    

📊 Reporte de Análisis CSV

Archivo: $(basename "$CSV_FILE")

Fecha: $(date)

📋 Resumen Ejecutivo

Total de registros: $TOTAL_RECORDS

Columnas: $COLUMNS

Formato: $([ "$DELIMITER" = "," ] && echo "CSV" || echo "TSV")

🔍 Estructura de Datos

$(head -1 "$CSV_FILE" | tr "$DELIMITER" '\n' | nl -w3 -s') ')

📊 Vista Previa de Datos

$(head -6 "$CSV_FILE")
EOF } > "$FINAL_REPORT" echo "🎯 ANÁLISIS COMPLETADO" echo "📁 Todos los reportes generados en: $REPORT_DIR/" echo "🌐 Reporte HTML principal: $FINAL_REPORT" echo echo "Archivos generados:" ls -la "$REPORT_DIR"/*"$TIMESTAMP"* 2>/dev/null || echo "No se generaron archivos con timestamp"

Ejercicios Prácticos

Ejercicio Final: Procesador de Datos de Ventas

Desarrolla un sistema completo para analizar datos de ventas:

# Usar el script desarrollado
chmod +x csv_analyzer.sh

# Crear datos de prueba más complejos
cat > ventas_2024.csv << 'EOF'
fecha,vendedor,producto,categoria,cantidad,precio_unitario,region,cliente
2024-01-15,Juan Pérez,Laptop HP,Tecnología,2,1200.00,Norte,Empresa A
2024-01-16,María González,Mouse Logitech,Accesorios,10,25.50,Sur,Particular B
2024-01-17,Carlos López,Monitor Samsung,Tecnología,1,300.00,Centro,Empresa C
2024-01-18,Ana Martín,Teclado Mecánico,Accesorios,3,150.00,Norte,Particular D
2024-01-19,Pedro Sánchez,Impresora Canon,Oficina,1,450.00,Sur,Empresa E
EOF

# Ejecutar análisis completo
./csv_analyzer.sh ventas_2024.csv

# Análisis adicional específico para ventas
echo "=== ANÁLISIS ESPECÍFICO DE VENTAS ==="

# Top productos por ingresos
echo "📈 Top productos por ingresos:"
awk -F',' 'NR>1 {ingresos[$3] += $5*$6} END {
    PROCINFO["sorted_in"] = "@val_num_desc"
    for(producto in ingresos) {
        printf "%-20s: €%.2f\n", producto, ingresos[producto]
    }
}' ventas_2024.csv

# Rendimiento por vendedor
echo -e "\n👤 Rendimiento por vendedor:"
awk -F',' 'NR>1 {
    ventas[$2] += $5*$6
    productos[$2]++
} END {
    for(vendedor in ventas) {
        printf "%-15s: €%.2f (%d productos)\n", vendedor, ventas[vendedor], productos[vendedor]
    }
}' ventas_2024.csv | sort -k2 -nr

# Análisis por región
echo -e "\n🗺️ Ventas por región:"
awk -F',' 'NR>1 {region[$7] += $5*$6} END {
    total = 0
    for(r in region) total += region[r]
    for(r in region) {
        pct = (region[r]/total)*100
        printf "%-10s: €%.2f (%.1f%%)\n", r, region[r], pct
    }
}' ventas_2024.csv

Tareas adicionales:

  • Crear alertas para ventas por debajo de cierto umbral
  • Generar reporte de tendencias por mes
  • Identificar clientes con mayor volumen de compras
  • Analizar estacionalidad por categoría de producto