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
¿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
# 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
# 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
# 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
# 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)
# 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
# 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.
#!/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