Quien viene de pandas sabe usar DataFrames.jl en cinco minutos. La API tiene la misma lógica conceptual: columnas con nombre, filtrado por condición, agrupaciones y combinaciones. La diferencia está en lo que ocurre por debajo: los tipos son estrictos, las columnas tienen un tipo declarado, y el compilador puede generar código nativo para las operaciones más comunes.
Crear un DataFrame
La forma más directa es pasar las columnas como pares nombre-valor al constructor:
using DataFrames, CSV
# Crear desde cero
df = DataFrame(
nombre = ["Ana", "Luis", "María", "Pedro"],
edad = [28, 34, 22, 41],
salario = [35000.0, 52000.0, 28000.0, 67000.0],
activo = [true, true, false, true]
)
# Ver los primeros registros
first(df, 3)
# Leer desde CSV
df2 = CSV.read("datos.csv", DataFrame)
# Ver tipos de columnas
describe(df)
El punto clave es que edad tiene tipo Int64 y salario tiene tipo Float64. No hay el problema de pandas donde una columna entera puede convertirse silenciosamente a object al aparecer un valor nulo.
Acceso y selección de datos
DataFrames.jl tiene dos convenciones de acceso que conviene conocer bien. El operador ! selecciona la columna sin copia (vista directa), mientras que el operador : crea una copia. Para operaciones de solo lectura usa !, para modificaciones usa la forma que no afecte al original.
# Acceder a columna (sin copia, más eficiente) df[!, :nombre] df[!, "nombre"] # equivalente # Acceder a columna (con copia) df[:, :salario] # Filas concretas df[1:3, :] # primeras tres filas, todas las columnas df[2, :nombre] # fila 2, columna nombre # Seleccionar columnas específicas df[:, [:nombre, :salario]] select(df, :nombre, :salario) # equivalente pero más flexible
Filtrar filas
La función filter acepta una condición como función anónima. Es más explícita que el indexado booleano de pandas, pero igualmente concisa:
# Filtrar: solo activos con salario mayor de 40000
df_filtrado = filter(row -> row.activo && row.salario > 40000, df)
# Alternativa con subset (más legible para múltiples condiciones)
df_subset = subset(df,
:activo => x -> x,
:salario => x -> x .> 40000
)
# Filtrar por valor concreto
df_ana = filter(:nombre => ==("Ana"), df)
# Filtrar con condición sobre string
df_jovenes = filter(:edad => <(30), df)
Agrupaciones y resúmenes
La combinación de groupby y combine es el equivalente a groupby().agg() de pandas, pero con una sintaxis más explícita sobre qué operación hacer sobre cada columna:
# Añadir columna de departamento para el ejemplo
df.departamento = ["IT", "Ventas", "IT", "Ventas"]
# Agrupar por departamento y calcular estadísticas
resultado = combine(
groupby(df, :departamento),
:salario => mean => :salario_medio,
:salario => maximum => :salario_max,
:edad => mean => :edad_media,
nrow => :total_empleados
)
La sintaxis :columna => funcion => :nombre_resultado es consistente y evita las ambigüedades que tiene pandas con agg. Una vez que te acostumbras, es más fácil de leer que la versión en Python.
Transformar y añadir columnas
# transform añade columnas sin eliminar las existentes
df_ampliado = transform(df,
:salario => (x -> x .* 1.1) => :salario_aumentado,
[:edad, :salario] => ((e, s) -> s ./ e) => :ratio_salario_edad
)
# Modificar columna en sitio (con !)
transform!(df, :nombre => (x -> uppercase.(x)) => :nombre)
# Añadir columna directamente
df[!, :bonificacion] = df.salario .* 0.05
Joins
Los joins funcionan como en SQL, con las mismas variantes: innerjoin, leftjoin, rightjoin, outerjoin:
# Tabla de departamentos con ciudad
departamentos = DataFrame(
departamento = ["IT", "Ventas"],
ciudad = ["Madrid", "Barcelona"]
)
# Join por la columna departamento
df_con_ciudad = leftjoin(df, departamentos, on = :departamento)
Rendimiento vs pandas
En operaciones simples, DataFrames.jl y pandas son comparables. La diferencia se nota en operaciones complejas sobre datasets grandes, donde Julia puede aprovechar la especialización por tipos y el compilador JIT. Un groupby + combine en un DataFrame de 10 millones de filas suele ser entre 2x y 4x más rápido en Julia, dependiendo de la operación.
Otro punto a favor es que puedes escribir funciones de transformación en Julia puro sin pensar en vectorización explícita: el compilador lo hace por ti. En pandas, si salís del núcleo de NumPy os vais rápidamente a velocidades de Python interpretado.
El análisis de datos en Julia se integra bien con el resto del ecosistema. Plots.jl y Makie leen DataFrames directamente, y Flux.jl puede recibir los datos preparados desde un DataFrame sin conversiones. Para comparar con otras herramientas de análisis, DuckDB es una alternativa interesante para análisis SQL embebido que también funciona desde Julia.
Imagen: Pexels / Daniil Komov
