Seaborn Lineplot: Guía completa de gráficos de líneas con sns.lineplot()
Updated on
Los gráficos de líneas son esenciales para visualizar tendencias, datos de series temporales y relaciones entre variables continuas. Sin embargo, crear gráficos de líneas con calidad de publicación que comuniquen claramente los insights suele requerir mucha configuración en matplotlib y estilizado manual. Los científicos de datos pierden horas ajustando colores de líneas, gestionando leyendas y formateando ejes, cuando deberían centrarse en el análisis.
La función lineplot() de Seaborn resuelve este problema al ofrecer una interfaz de alto nivel para crear gráficos de líneas atractivos con un mínimo de código. Maneja automáticamente la agregación estadística, los intervalos de confianza, las paletas de color y el estilo visual, manteniéndose a la vez altamente personalizable para casos de uso avanzados.
Esta guía cubre desde gráficos de líneas básicos hasta visualizaciones avanzadas con múltiples series y estilo personalizado. Aprenderás a crear gráficos profesionales que comuniquen eficazmente los insights de tus datos.
Comprender los fundamentos de sns.lineplot()
La función sns.lineplot() crea gráficos de líneas a partir de DataFrames de pandas o arrays. Agrega automáticamente múltiples observaciones en cada valor de x y muestra intervalos de confianza por defecto.
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
# Create sample data
df = pd.DataFrame({
'time': range(10),
'value': [1, 3, 2, 5, 4, 6, 5, 7, 6, 8]
})
# Basic lineplot
sns.lineplot(data=df, x='time', y='value')
plt.title('Basic Line Plot')
plt.show()Esto crea un gráfico de líneas limpio con etiquetas de ejes automáticas derivadas de los nombres de las columnas. La función se encarga del formateo de datos, la escala y el estilo visual sin configuración manual.
Parámetros principales y sintaxis
La función sns.lineplot() acepta datos en múltiples formatos y ofrece amplias opciones de personalización:
sns.lineplot(
data=None, # DataFrame, array, or dict
x=None, # Column name or vector for x-axis
y=None, # Column name or vector for y-axis
hue=None, # Grouping variable for color
size=None, # Grouping variable for line width
style=None, # Grouping variable for line style
palette=None, # Color palette
markers=False, # Add markers to data points
dashes=True, # Use dashed lines for styles
ci=95, # Confidence interval (deprecated in newer versions)
errorbar=('ci', 95), # Error representation
legend='auto', # Legend display
ax=None # Matplotlib axes object
)Al trabajar con DataFrames, usar nombres de columna para los parámetros x e y hace el código más claro:
# Create multi-observation dataset
data = pd.DataFrame({
'day': [1, 2, 3, 1, 2, 3, 1, 2, 3],
'sales': [100, 150, 120, 110, 145, 125, 105, 155, 130],
'store': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C']
})
# Plot with automatic aggregation
sns.lineplot(data=data, x='day', y='sales')
plt.title('Average Sales by Day (with 95% CI)')
plt.xlabel('Day of Week')
plt.ylabel('Sales ($)')
plt.show()La función calcula automáticamente la media de ventas por día y muestra los intervalos de confianza como una banda sombreada alrededor de la línea.
Graficar múltiples líneas con Hue
El parámetro hue crea líneas separadas para distintos grupos en tus datos, asignando automáticamente colores distintos:
# Multiple stores on same plot
fig, ax = plt.subplots(figsize=(10, 6))
sns.lineplot(
data=data,
x='day',
y='sales',
hue='store', # Separate line for each store
palette='Set2'
)
plt.title('Sales Comparison Across Stores')
plt.xlabel('Day of Week')
plt.ylabel('Sales ($)')
plt.legend(title='Store', loc='upper left')
plt.show()Esto crea tres líneas de colores distintos con generación automática de la leyenda. El parámetro palette controla el esquema de colores.
Para escenarios de agrupación más complejos:
# Multiple grouping variables
customer_data = pd.DataFrame({
'month': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3],
'revenue': [5000, 5500, 6000, 4800, 5200, 5800, 6200, 6800, 7200, 6000, 6500, 7000],
'segment': ['Premium', 'Premium', 'Premium', 'Standard', 'Standard', 'Standard',
'Premium', 'Premium', 'Premium', 'Standard', 'Standard', 'Standard'],
'region': ['North', 'North', 'North', 'North', 'North', 'North',
'South', 'South', 'South', 'South', 'South', 'South']
})
# Use hue for segment, style for region
sns.lineplot(
data=customer_data,
x='month',
y='revenue',
hue='segment',
style='region',
markers=True,
dashes=False
)
plt.title('Revenue by Segment and Region')
plt.xlabel('Month')
plt.ylabel('Revenue ($)')
plt.show()Personalizar estilos de línea con el parámetro Style
El parámetro style varía la apariencia de la línea usando distintos patrones de guiones o tipos de línea:
# Temperature data with different sensors
temp_data = pd.DataFrame({
'hour': list(range(24)) * 3,
'temperature': [15, 14, 13, 12, 12, 13, 15, 17, 19, 21, 23, 24,
25, 26, 25, 24, 22, 20, 18, 17, 16, 15, 15, 14] * 3,
'sensor': ['Sensor_A'] * 24 + ['Sensor_B'] * 24 + ['Sensor_C'] * 24,
'location': ['Indoor'] * 24 + ['Outdoor'] * 24 + ['Basement'] * 24
})
# Add some variation
import numpy as np
np.random.seed(42)
temp_data['temperature'] = temp_data['temperature'] + np.random.normal(0, 1, len(temp_data))
fig, ax = plt.subplots(figsize=(12, 6))
sns.lineplot(
data=temp_data,
x='hour',
y='temperature',
hue='location',
style='location',
markers=True,
dashes=True,
palette='tab10'
)
plt.title('24-Hour Temperature Monitoring')
plt.xlabel('Hour of Day')
plt.ylabel('Temperature (°C)')
plt.legend(title='Location')
plt.grid(True, alpha=0.3)
plt.show()Los distintos estilos de línea ayudan a distinguir grupos al imprimir en escala de grises o por consideraciones de accesibilidad.
Añadir marcadores a los puntos de datos
Los marcadores resaltan puntos de datos individuales a lo largo de la línea, lo cual es útil para datos dispersos o para enfatizar observaciones específicas:
# Quarterly earnings data
earnings = pd.DataFrame({
'quarter': ['Q1', 'Q2', 'Q3', 'Q4'] * 3,
'earnings': [2.1, 2.3, 2.5, 2.4, 2.2, 2.4, 2.6, 2.7, 2.3, 2.5, 2.8, 2.9],
'year': ['2023'] * 4 + ['2024'] * 4 + ['2025'] * 4
})
sns.lineplot(
data=earnings,
x='quarter',
y='earnings',
hue='year',
markers=True,
marker='o', # Specific marker style
markersize=8,
linewidth=2.5,
palette='deep'
)
plt.title('Quarterly Earnings per Share')
plt.xlabel('Quarter')
plt.ylabel('EPS ($)')
plt.ylim(2.0, 3.0)
plt.legend(title='Year')
plt.grid(True, alpha=0.3)
plt.show()Puedes especificar estilos de marcador por grupo:
# Custom markers for different groups
sns.lineplot(
data=earnings,
x='quarter',
y='earnings',
hue='year',
style='year',
markers=['o', 's', '^'], # Different marker per year
markersize=10,
dashes=False
)
plt.title('Earnings Trend with Distinct Markers')
plt.show()Trabajar con intervalos de confianza y bandas de error
Seaborn calcula automáticamente intervalos de confianza cuando existen múltiples observaciones en cada valor de x. Esta agregación estadística ayuda a comunicar la incertidumbre de los datos:
# Experimental data with replicates
experiment = pd.DataFrame({
'concentration': [0.1, 0.5, 1.0, 2.0, 5.0] * 10,
'response': np.random.lognormal(mean=[1, 1.5, 2, 2.5, 3] * 10, sigma=0.3),
'replicate': list(range(10)) * 5
})
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# 95% confidence interval (default)
sns.lineplot(data=experiment, x='concentration', y='response',
errorbar=('ci', 95), ax=axes[0])
axes[0].set_title('95% Confidence Interval')
# Standard deviation
sns.lineplot(data=experiment, x='concentration', y='response',
errorbar='sd', ax=axes[1])
axes[1].set_title('Standard Deviation')
# No error bars
sns.lineplot(data=experiment, x='concentration', y='response',
errorbar=None, ax=axes[2])
axes[2].set_title('No Error Bars')
plt.tight_layout()
plt.show()El parámetro errorbar admite múltiples representaciones:
('ci', 95): intervalo de confianza del 95%('pi', 95): intervalo de predicción del 95%'sd': desviación estándar'se': error estándar('pi', 50): rango intercuartílicoNone: sin representación del error
Personalizar colores con paletas
El parámetro palette controla los colores de las líneas usando paletas con nombre o listas de colores personalizadas:
# Stock price comparison
stocks = pd.DataFrame({
'date': pd.date_range('2025-01-01', periods=60),
'price': np.random.randn(60).cumsum() + 100,
'ticker': ['AAPL'] * 60
})
stocks = pd.concat([
stocks,
pd.DataFrame({
'date': pd.date_range('2025-01-01', periods=60),
'price': np.random.randn(60).cumsum() + 150,
'ticker': ['GOOGL'] * 60
}),
pd.DataFrame({
'date': pd.date_range('2025-01-01', periods=60),
'price': np.random.randn(60).cumsum() + 200,
'ticker': ['MSFT'] * 60
})
])
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Built-in palette
sns.lineplot(data=stocks, x='date', y='price', hue='ticker',
palette='Set1', ax=axes[0, 0])
axes[0, 0].set_title('Set1 Palette')
# Custom colors
custom_colors = {'AAPL': '#FF6B6B', 'GOOGL': '#4ECDC4', 'MSFT': '#45B7D1'}
sns.lineplot(data=stocks, x='date', y='price', hue='ticker',
palette=custom_colors, ax=axes[0, 1])
axes[0, 1].set_title('Custom Color Mapping')
# Colorblind-safe palette
sns.lineplot(data=stocks, x='date', y='price', hue='ticker',
palette='colorblind', ax=axes[1, 0])
axes[1, 0].set_title('Colorblind Palette')
# Dark palette
sns.lineplot(data=stocks, x='date', y='price', hue='ticker',
palette='dark', ax=axes[1, 1])
axes[1, 1].set_title('Dark Palette')
plt.tight_layout()
plt.show()Opciones de paleta populares incluyen:
'deep','muted','pastel','bright','dark','colorblind''Set1','Set2','Set3','Paired','tab10''viridis','plasma','inferno','magma','cividis'
Visualización de series temporales
Seaborn lineplot destaca en la visualización de series temporales con formateo automático de fechas:
# Website traffic data
dates = pd.date_range('2025-01-01', periods=180, freq='D')
traffic = pd.DataFrame({
'date': dates,
'visits': 1000 + np.random.randn(180).cumsum() * 50 + np.sin(np.arange(180) / 7) * 200,
'source': ['Organic'] * 180
})
# Add paid traffic
paid = pd.DataFrame({
'date': dates,
'visits': 500 + np.random.randn(180).cumsum() * 30,
'source': ['Paid'] * 180
})
traffic = pd.concat([traffic, paid])
fig, ax = plt.subplots(figsize=(14, 6))
sns.lineplot(
data=traffic,
x='date',
y='visits',
hue='source',
palette={'Organic': '#2ecc71', 'Paid': '#e74c3c'},
linewidth=2
)
plt.title('Website Traffic Over Time', fontsize=16, fontweight='bold')
plt.xlabel('Date', fontsize=12)
plt.ylabel('Daily Visits', fontsize=12)
plt.legend(title='Traffic Source', title_fontsize=11, fontsize=10)
plt.grid(True, alpha=0.3, linestyle='--')
# Format x-axis dates
import matplotlib.dates as mdates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()Para datos con índice datetime:
# Create time series with datetime index
ts_data = pd.DataFrame({
'value': np.random.randn(365).cumsum() + 50
}, index=pd.date_range('2025-01-01', periods=365))
# Reset index to use in lineplot
ts_data_reset = ts_data.reset_index()
ts_data_reset.columns = ['date', 'value']
sns.lineplot(data=ts_data_reset, x='date', y='value')
plt.title('Time Series with Datetime Index')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()Graficar múltiples líneas desde datos en formato ancho (wide)
Los DataFrames en formato ancho almacenan distintas variables en columnas separadas. Seaborn puede graficarlas directamente:
# Wide format data
wide_data = pd.DataFrame({
'month': range(1, 13),
'Product_A': [100, 120, 115, 130, 140, 135, 150, 160, 155, 170, 180, 175],
'Product_B': [80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135],
'Product_C': [60, 65, 70, 68, 75, 80, 85, 90, 88, 95, 100, 105]
})
# Method 1: Melt to long format
long_data = wide_data.melt(id_vars='month', var_name='Product', value_name='Sales')
sns.lineplot(data=long_data, x='month', y='Sales', hue='Product')
plt.title('Product Sales Comparison')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.show()Alternativamente, puedes graficar directamente desde formato ancho:
# Method 2: Plot each column separately
fig, ax = plt.subplots(figsize=(10, 6))
for column in ['Product_A', 'Product_B', 'Product_C']:
sns.lineplot(data=wide_data, x='month', y=column, label=column, ax=ax)
plt.title('Product Sales (Wide Format)')
plt.xlabel('Month')
plt.ylabel('Sales')
plt.legend(title='Product')
plt.show()Combinar con Matplotlib para personalización avanzada
Seaborn lineplot se integra perfectamente con matplotlib para un control fino:
# Create figure with custom styling
fig, ax = plt.subplots(figsize=(12, 7))
# Set overall style
sns.set_style('whitegrid')
sns.set_context('notebook', font_scale=1.2)
# Plot data
performance = pd.DataFrame({
'epoch': list(range(1, 51)) * 2,
'accuracy': np.concatenate([
0.6 + 0.008 * np.arange(50) + np.random.randn(50) * 0.02,
0.55 + 0.009 * np.arange(50) + np.random.randn(50) * 0.025
]),
'model': ['Model_A'] * 50 + ['Model_B'] * 50
})
sns.lineplot(
data=performance,
x='epoch',
y='accuracy',
hue='model',
palette=['#FF6B6B', '#4ECDC4'],
linewidth=2.5,
ax=ax
)
# Customize with matplotlib
ax.set_title('Model Training Performance', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Training Epoch', fontsize=14, fontweight='bold')
ax.set_ylabel('Validation Accuracy', fontsize=14, fontweight='bold')
ax.set_ylim(0.5, 1.0)
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
# Add reference line
ax.axhline(y=0.8, color='gray', linestyle='--', linewidth=1.5, alpha=0.7, label='Target (80%)')
# Customize legend
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, title='Model', title_fontsize=12,
fontsize=11, loc='lower right', framealpha=0.95)
# Add grid customization
ax.grid(True, alpha=0.4, linestyle=':', linewidth=0.8)
ax.set_facecolor('#F8F9FA')
# Add annotations
ax.annotate('Model A reaches 90%',
xy=(45, 0.9), xytext=(35, 0.85),
arrowprops=dict(arrowstyle='->', color='#FF6B6B', lw=1.5),
fontsize=10, color='#FF6B6B', fontweight='bold')
plt.tight_layout()
plt.show()Crear subplots con múltiples lineplots
Compara distintos aspectos de los datos usando diseños con subplots:
# Multi-metric dashboard
metrics = pd.DataFrame({
'time': list(range(100)) * 3,
'cpu_usage': np.random.rand(300) * 60 + 20 + np.sin(np.arange(300) / 10) * 15,
'memory_usage': np.random.rand(300) * 40 + 40 + np.cos(np.arange(300) / 15) * 10,
'disk_io': np.random.rand(300) * 80 + 10 + np.sin(np.arange(300) / 8) * 20,
'server': ['Server_1'] * 100 + ['Server_2'] * 100 + ['Server_3'] * 100
})
fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True)
# CPU Usage
sns.lineplot(data=metrics, x='time', y='cpu_usage', hue='server',
palette='Set2', ax=axes[0], legend=True)
axes[0].set_title('CPU Usage (%)', fontsize=14, fontweight='bold')
axes[0].set_ylabel('Usage (%)')
axes[0].set_xlabel('')
axes[0].axhline(y=80, color='red', linestyle='--', alpha=0.5, label='Threshold')
axes[0].legend(loc='upper left', ncol=4)
# Memory Usage
sns.lineplot(data=metrics, x='time', y='memory_usage', hue='server',
palette='Set2', ax=axes[1], legend=False)
axes[1].set_title('Memory Usage (%)', fontsize=14, fontweight='bold')
axes[1].set_ylabel('Usage (%)')
axes[1].set_xlabel('')
axes[1].axhline(y=80, color='red', linestyle='--', alpha=0.5)
# Disk I/O
sns.lineplot(data=metrics, x='time', y='disk_io', hue='server',
palette='Set2', ax=axes[2], legend=False)
axes[2].set_title('Disk I/O (MB/s)', fontsize=14, fontweight='bold')
axes[2].set_ylabel('Throughput')
axes[2].set_xlabel('Time (seconds)', fontsize=12)
plt.tight_layout()
plt.show()Diseños en cuadrícula para análisis comparativo:
# 2x2 comparison grid
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
categories = ['Category_A', 'Category_B', 'Category_C', 'Category_D']
positions = [(0, 0), (0, 1), (1, 0), (1, 1)]
for category, (i, j) in zip(categories, positions):
subset = pd.DataFrame({
'x': range(20),
'y': np.random.randn(20).cumsum() + 10
})
sns.lineplot(data=subset, x='x', y='y', ax=axes[i, j],
marker='o', linewidth=2, color='#3498db')
axes[i, j].set_title(f'{category} Trend', fontweight='bold')
axes[i, j].grid(True, alpha=0.3)
plt.suptitle('Multi-Category Performance Dashboard', fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()Tabla comparativa: sns.lineplot vs plt.plot vs sns.relplot
| Feature | sns.lineplot() | plt.plot() | sns.relplot(kind="line") |
|---|---|---|---|
| Automatic aggregation | Yes (mean + CI) | No | Yes (mean + CI) |
| DataFrame integration | Native support | Requires array conversion | Native support |
| Multiple groups (hue) | Automatic coloring | Manual iteration | Automatic coloring |
| Confidence intervals | Built-in | Manual calculation | Built-in |
| FacetGrid support | No (single axes) | No | Yes (automatic subplots) |
| Statistical estimation | Mean, median, etc. | None | Mean, median, etc. |
| Semantic mappings | hue, size, style | Manual | hue, size, style, col, row |
| Default styling | Seaborn theme | Matplotlib default | Seaborn theme |
| Legend handling | Automatic | Manual | Automatic |
| Code complexity | Low | Medium | Low |
| Performance (large data) | Medium | Fast | Medium |
| Customization depth | High (via ax) | Highest | Medium (FacetGrid limits) |
| Best use case | Single plot, grouped data | Simple plots, full control | Multi-facet comparisons |
Usa sns.lineplot() cuando necesites agregación automática y visualización estadística en un solo gráfico.
Usa plt.plot() cuando necesites el máximo control, rendimiento o graficar datos ya agregados.
Usa sns.relplot(kind="line") cuando necesites gráficos facetados con generación automática de subplots.
Tabla de referencia de parámetros
| Parameter | Type | Default | Description |
|---|---|---|---|
data | DataFrame, dict, array | None | Estructura de datos de entrada |
x, y | str, array | None | Variables para los ejes x e y |
hue | str | None | Variable de agrupación para codificar color |
size | str | None | Variable de agrupación para el ancho de línea |
style | str | None | Variable de agrupación para patrones de guiones |
palette | str, list, dict | None | Paleta de colores para niveles de hue |
hue_order | list | None | Orden de niveles para la variable hue |
units | str | None | Agrupación por unidad de muestreo (sin agregación) |
estimator | function | np.mean | Función de agregación (media, mediana, etc.) |
errorbar | tuple, str | ('ci', 95) | Método de representación del error |
n_boot | int | 1000 | Iteraciones bootstrap para CI |
seed | int | None | Semilla aleatoria para bootstrap |
sort | bool | True | Ordenar la variable x antes de graficar |
err_style | str | 'band' | 'band' o 'bars' para mostrar errores |
err_kws | dict | None | Argumentos keyword para la representación del error |
markers | bool, list | False | Estilos de marcador para puntos |
dashes | bool, list | True | Patrones de guiones de la línea |
legend | str, bool | 'auto' | Comportamiento de visualización de la leyenda |
ci | int, 'sd', None | Deprecated | Usa errorbar en su lugar |
ax | Axes | None | Objeto Axes de matplotlib |
linewidth | float | 1.5 | Ancho de la línea |
linestyle | str | '-' | Estilo de línea ('-', '--', '-.', ':') |
Ejemplo del mundo real: análisis de precios de acciones
# Simulate stock price data
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=252, freq='B') # Business days
stocks_real = pd.DataFrame({
'Date': np.tile(dates, 4),
'Price': np.concatenate([
100 * np.exp(np.random.randn(252).cumsum() * 0.01), # AAPL
150 * np.exp(np.random.randn(252).cumsum() * 0.012), # GOOGL
200 * np.exp(np.random.randn(252).cumsum() * 0.011), # MSFT
80 * np.exp(np.random.randn(252).cumsum() * 0.015) # TSLA
]),
'Ticker': ['AAPL'] * 252 + ['GOOGL'] * 252 + ['MSFT'] * 252 + ['TSLA'] * 252
})
# Calculate normalized returns (base = 100)
stocks_normalized = stocks_real.copy()
for ticker in stocks_normalized['Ticker'].unique():
mask = stocks_normalized['Ticker'] == ticker
first_price = stocks_normalized.loc[mask, 'Price'].iloc[0]
stocks_normalized.loc[mask, 'Normalized_Return'] = (
stocks_normalized.loc[mask, 'Price'] / first_price * 100
)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10))
# Absolute prices
sns.lineplot(
data=stocks_real,
x='Date',
y='Price',
hue='Ticker',
palette='deep',
linewidth=2,
ax=ax1
)
ax1.set_title('Stock Prices - Absolute Values (2024)', fontsize=16, fontweight='bold')
ax1.set_xlabel('')
ax1.set_ylabel('Price ($)', fontsize=12)
ax1.legend(title='Ticker', title_fontsize=11, fontsize=10, loc='upper left')
ax1.grid(True, alpha=0.3)
# Normalized returns
sns.lineplot(
data=stocks_normalized,
x='Date',
y='Normalized_Return',
hue='Ticker',
palette='deep',
linewidth=2,
ax=ax2
)
ax2.axhline(y=100, color='gray', linestyle='--', linewidth=1, alpha=0.7)
ax2.set_title('Normalized Returns (Base = 100)', fontsize=16, fontweight='bold')
ax2.set_xlabel('Date', fontsize=12)
ax2.set_ylabel('Normalized Return', fontsize=12)
ax2.legend(title='Ticker', title_fontsize=11, fontsize=10, loc='upper left')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Ejemplo del mundo real: monitoreo de datos de sensores
# IoT sensor data with noise
hours = np.linspace(0, 24, 288) # 5-minute intervals
sensor_data = pd.DataFrame({
'time': np.tile(hours, 4),
'temperature': np.concatenate([
20 + 5 * np.sin(hours * np.pi / 12) + np.random.randn(288) * 0.5, # Room 1
22 + 4 * np.sin(hours * np.pi / 12 - 0.5) + np.random.randn(288) * 0.7, # Room 2
19 + 6 * np.sin(hours * np.pi / 12 + 0.3) + np.random.randn(288) * 0.6, # Room 3
21 + 5.5 * np.sin(hours * np.pi / 12 - 0.2) + np.random.randn(288) * 0.8 # Room 4
]),
'room': ['Room_1'] * 288 + ['Room_2'] * 288 + ['Room_3'] * 288 + ['Room_4'] * 288,
'building': ['Building_A'] * 576 + ['Building_B'] * 576
})
fig, ax = plt.subplots(figsize=(14, 7))
sns.lineplot(
data=sensor_data,
x='time',
y='temperature',
hue='room',
style='building',
palette='tab10',
linewidth=2,
markers=False,
errorbar=('ci', 68), # 1 standard deviation
ax=ax
)
# Add comfort zone
ax.axhspan(18, 24, alpha=0.1, color='green', label='Comfort Zone')
ax.set_title('24-Hour Temperature Monitoring Across Rooms', fontsize=16, fontweight='bold')
ax.set_xlabel('Hour of Day', fontsize=12)
ax.set_ylabel('Temperature (°C)', fontsize=12)
ax.set_xticks(range(0, 25, 2))
ax.legend(title='Location', bbox_to_anchor=(1.05, 1), loc='upper left')
ax.grid(True, alpha=0.3, linestyle=':')
plt.tight_layout()
plt.show()Ejemplo del mundo real: resultados de una prueba A/B a lo largo del tiempo
# A/B test conversion rates over time
days = np.arange(1, 31)
ab_test = pd.DataFrame({
'day': np.tile(days, 2),
'conversion_rate': np.concatenate([
0.05 + 0.001 * days + np.random.randn(30) * 0.005, # Control
0.055 + 0.0012 * days + np.random.randn(30) * 0.005 # Variant
]) * 100,
'variant': ['Control'] * 30 + ['Variant_B'] * 30,
'sample_size': np.random.randint(800, 1200, 60)
})
fig, axes = plt.subplots(2, 1, figsize=(12, 9), sharex=True)
# Conversion rate trend
sns.lineplot(
data=ab_test,
x='day',
y='conversion_rate',
hue='variant',
palette={'Control': '#95a5a6', 'Variant_B': '#27ae60'},
linewidth=2.5,
markers=True,
markersize=6,
errorbar=None,
ax=axes[0]
)
axes[0].set_title('A/B Test: Conversion Rate Over Time', fontsize=16, fontweight='bold')
axes[0].set_ylabel('Conversion Rate (%)', fontsize=12)
axes[0].set_xlabel('')
axes[0].legend(title='Test Group', fontsize=11)
axes[0].grid(True, alpha=0.3)
# Sample size tracking
sns.lineplot(
data=ab_test,
x='day',
y='sample_size',
hue='variant',
palette={'Control': '#95a5a6', 'Variant_B': '#27ae60'},
linewidth=2,
markers=False,
errorbar=None,
ax=axes[1]
)
axes[1].set_title('Daily Sample Size', fontsize=14, fontweight='bold')
axes[1].set_ylabel('Users', fontsize=12)
axes[1].set_xlabel('Day of Test', fontsize=12)
axes[1].legend(title='Test Group', fontsize=11)
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Guardar y exportar gráficos de líneas
Guarda gráficos en varios formatos para informes y presentaciones:
# Create a polished chart for export
fig, ax = plt.subplots(figsize=(10, 6), dpi=100)
export_data = pd.DataFrame({
'month': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] * 2,
'revenue': [50, 55, 53, 60, 65, 70, 45, 48, 50, 55, 58, 63],
'region': ['North'] * 6 + ['South'] * 6
})
sns.lineplot(
data=export_data,
x='month',
y='revenue',
hue='region',
palette='Set1',
linewidth=3,
markers=True,
markersize=10,
ax=ax
)
ax.set_title('Regional Revenue Comparison', fontsize=16, fontweight='bold', pad=15)
ax.set_xlabel('Month', fontsize=13)
ax.set_ylabel('Revenue ($K)', fontsize=13)
ax.legend(title='Region', title_fontsize=12, fontsize=11)
ax.grid(True, alpha=0.4)
# Save in multiple formats
plt.savefig('revenue_comparison.png', dpi=300, bbox_inches='tight') # High-res PNG
plt.savefig('revenue_comparison.pdf', bbox_inches='tight') # Vector PDF
plt.savefig('revenue_comparison.svg', bbox_inches='tight') # SVG for web
# Save with transparent background
plt.savefig('revenue_comparison_transparent.png', dpi=300,
bbox_inches='tight', transparent=True)
plt.show()
print("Charts saved in multiple formats:")
print("- revenue_comparison.png (300 DPI)")
print("- revenue_comparison.pdf (vector)")
print("- revenue_comparison.svg (web)")
print("- revenue_comparison_transparent.png (transparent)")Guardar con dimensiones específicas:
# Set exact figure size for publication
fig = plt.figure(figsize=(8, 5)) # Width, height in inches
ax = fig.add_subplot(111)
sns.lineplot(data=export_data, x='month', y='revenue', hue='region', ax=ax)
ax.set_title('Revenue Trends')
# Save with exact pixel dimensions (at 100 DPI: 8 inches * 100 = 800 pixels)
plt.savefig('chart_800x500.png', dpi=100, bbox_inches='tight')
# Save with higher resolution (8 inches * 300 DPI = 2400 pixels)
plt.savefig('chart_2400x1500.png', dpi=300, bbox_inches='tight')
plt.close()Gráficos de líneas interactivos con PyGWalker
Para la exploración interactiva de datos de gráficos de líneas, considera PyGWalker, una librería open-source de Python que convierte DataFrames en visualizaciones interactivas tipo Tableau:
# Install PyGWalker: pip install pygwalker
import pygwalker as pyg
import pandas as pd
import numpy as np
# Create time series data
dates = pd.date_range('2025-01-01', periods=365, freq='D')
interactive_data = pd.DataFrame({
'Date': dates,
'Sales': 1000 + np.random.randn(365).cumsum() * 50,
'Costs': 600 + np.random.randn(365).cumsum() * 30,
'Region': np.random.choice(['North', 'South', 'East', 'West'], 365),
'Product': np.random.choice(['Product_A', 'Product_B', 'Product_C'], 365)
})
# Launch interactive explorer
walker = pyg.walk(interactive_data)PyGWalker proporciona:
- Interfaz de arrastrar y soltar para crear gráficos de líneas
- Filtrado y agrupación interactivos
- Agregación automática y binning de fechas
- Comparación de múltiples series
- Exportación a gráficos estáticos
- Sin necesidad de escribir código manual para cada visualización
Esto es especialmente útil para el análisis exploratorio de datos cuando necesitas probar rápidamente diferentes agrupaciones, rangos de tiempo y agregaciones sin escribir código para cada variación. Visita github.com/Kanaries/pygwalker (opens in a new tab) para instalación y documentación.
Consejos avanzados y buenas prácticas
Manejo de datasets grandes
Para datasets con miles de puntos por serie, considera hacer downsampling o agregación:
# Large dataset simulation
large_data = pd.DataFrame({
'timestamp': pd.date_range('2025-01-01', periods=10000, freq='T'),
'value': np.random.randn(10000).cumsum()
})
# Method 1: Downsample to hourly
hourly = large_data.set_index('timestamp').resample('H').mean().reset_index()
sns.lineplot(data=hourly, x='timestamp', y='value')
plt.title('Downsampled to Hourly Average')
plt.show()
# Method 2: Use estimator for automatic aggregation
large_data['hour'] = large_data['timestamp'].dt.floor('H')
sns.lineplot(data=large_data, x='hour', y='value', estimator='median')
plt.title('Median Aggregation by Hour')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()Combinar múltiples estimadores
Compara diferentes agregaciones estadísticas:
# Noisy experimental data
experiment_multi = pd.DataFrame({
'dose': [0.1, 0.5, 1.0, 2.0, 5.0] * 20,
'response': np.random.lognormal([1, 1.5, 2, 2.5, 3] * 20, 0.4)
})
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# Mean with CI
sns.lineplot(data=experiment_multi, x='dose', y='response',
estimator='mean', errorbar=('ci', 95), ax=axes[0])
axes[0].set_title('Mean ± 95% CI')
# Median with IQR
sns.lineplot(data=experiment_multi, x='dose', y='response',
estimator='median', errorbar=('pi', 50), ax=axes[1])
axes[1].set_title('Median ± IQR')
# Custom estimator (75th percentile)
sns.lineplot(data=experiment_multi, x='dose', y='response',
estimator=lambda x: np.percentile(x, 75), errorbar=None, ax=axes[2])
axes[2].set_title('75th Percentile')
plt.tight_layout()
plt.show()Resaltar regiones específicas
Llama la atención sobre periodos importantes:
# Sales data with promotion period
sales_highlight = pd.DataFrame({
'week': range(1, 53),
'sales': 1000 + np.random.randn(52).cumsum() * 100 +
np.where((np.arange(52) >= 20) & (np.arange(52) <= 30), 500, 0)
})
fig, ax = plt.subplots(figsize=(12, 6))
sns.lineplot(data=sales_highlight, x='week', y='sales', linewidth=2.5, color='#3498db')
# Highlight promotion period
ax.axvspan(20, 30, alpha=0.2, color='gold', label='Promotion Period')
ax.axhline(y=sales_highlight['sales'].mean(), color='red',
linestyle='--', linewidth=1.5, alpha=0.7, label='Average Sales')
ax.set_title('Sales Performance with Promotion Period Highlighted',
fontsize=16, fontweight='bold')
ax.set_xlabel('Week', fontsize=12)
ax.set_ylabel('Sales ($)', fontsize=12)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Preguntas frecuentes (FAQ)
Conclusión
La función lineplot() de Seaborn proporciona una interfaz potente y accesible para crear gráficos de líneas con calidad de publicación. Desde la visualización básica de tendencias hasta comparaciones complejas de múltiples series con agregación estadística, se encarga de tareas comunes de graficación con poco código, manteniendo la personalización completa mediante la integración con matplotlib.
El manejo automático de intervalos de confianza, el mapeo semántico de color y la integración con DataFrames eliminan código repetitivo y te permiten centrarte en los insights en lugar de en la mecánica del gráfico. Ya sea para visualizar tendencias de series temporales, comparar grupos experimentales o monitorizar métricas de sistemas, sns.lineplot() ofrece visualizaciones limpias e informativas que comunican de forma efectiva.
Para gráficos estáticos de publicación, combina la interfaz de alto nivel de seaborn con el control detallado de matplotlib. Para exploración interactiva y prototipado rápido, herramientas como PyGWalker extienden estas capacidades a interfaces de arrastrar y soltar que no requieren código para cada iteración de visualización.
Domina estas técnicas para transformar datos en bruto en historias visuales claras que impulsen la toma de decisiones y comuniquen insights tanto a audiencias técnicas como no técnicas.