Altair: Heatmap smoothing/interpolation in Altair

Created on 28 Mar 2020  路  3Comments  路  Source: altair-viz/altair

I'm trying to achieve an effect similar to bicubic interpolation in matlotlib.

Here is the minimal code in matlotlib:

%pylab inline

import matplotlib.pyplot as plt
import numpy as np

grid = np.random.rand(4, 4)

plt.subplot(121)
plt.imshow(grid)
plt.subplot(122)
plt.imshow(grid, interpolation = 'bicubic')

which produces:

download

And here is a starter code from the simple heat map Altair example:

import altair as alt
import pandas as pd
import numpy as np

# Compute x^2 + y^2 across a 2D grid
x, y = np.meshgrid(range(-5, 5), range(-5, 5))
z = x ** 2 + y ** 2

# Convert this grid to columnar data expected by Altair
source = pd.DataFrame({'x': x.ravel(),
                     'y': y.ravel(),
                     'z': z.ravel()})

alt.Chart(source).mark_rect().encode(
    x='x:O',
    y='y:O',
    color='z:Q'
)

visualization-26


P.S. there are other interpolation schemes as well and many of these will probably work for me, in case bicubic specifically isn't available.

download

enhancement vega-lite-related

All 3 comments

This is not yet possible in Altair. The Vega-Lite feature request is here: https://github.com/vega/vega-lite/issues/6043

@firasm You can do this outside of altair -- I followed an example here and by controlling the pixel_size and the method in scipy's griddata (there's definitely a cubic option, a few others as well...), I think you can get what you're looking for!

e.g. something along the lines of:

from scipy.interpolate import griddata
import numpy as np
import pandas as pd

# Given your dataframe = df

# Define size of pixels in grid -- smaller = smoother grid
pixel_size = .08

# Determine extent of observations and create pixel_size-spaced array
x_range = np.arange(df.x.min() - df.x.min() % pixel,
                    df.x.max(), pixel)
y_range = np.arange(df.y.min() - df.y.min() % pixel,
                    df.y.max(), pixel)[::-1]
shape = (len(y_range), len(x_range))
xmin, xmax, ymin, ymax = x_range.min(), x_range.max(), y_range.min(), y_range.max()
extent = (xmin, xmax, ymin, ymax)
# Create grid
x_mesh, y_mesh = np.meshgrid(x_range, y_range)

# Create dataframe to store interpolated points in
interp_df = pd.DataFrame({'y':y_mesh.flatten(), 'x': x_mesh.flatten()})

# Interpolate using desired method with scipy's griddata
pm_interp = griddata((df['longitude'], df['latitude']), df[date], (x_mesh, y_mesh), method = 'linear')
interp_df['interpolated value'] = pm_interp.flatten()

Then you can plot in altair your interp_df as a heatmap.

Great! Thanks @cgostic !

For anyone interested, here's what the final product looks like and the associated code:

import altair as alt
alt.data_transformers.disable_max_rows()

import pandas as pd
import numpy as np
from scipy.interpolate import griddata

# Compute x^2 + y^2 across a 2D grid
x, y = np.meshgrid(range(-5, 5), range(-5, 5))
z = x ** 2 + y ** 2

# Convert this grid to columnar data expected by Altair
source = pd.DataFrame({'x': x.ravel(),
                     'y': y.ravel(),
                     'z': z.ravel()})

# ---------
df = source

# Define size of pixels in grid -- smaller = smoother grid
pixel_size = .10

# Determine extent of observations and create pixel_size-spaced array
x_range = np.arange(df.x.min() - df.x.min() % pixel_size,
                    df.x.max(), pixel_size)
y_range = np.arange(df.y.min() - df.y.min() % pixel_size,
                    df.y.max(), pixel_size)[::-1]
shape = (len(y_range), len(x_range))
xmin, xmax, ymin, ymax = x_range.min(), x_range.max(), y_range.min(), y_range.max()
extent = (xmin, xmax, ymin, ymax)

# Create grid
x_mesh, y_mesh = np.meshgrid(x_range, y_range)

# Create dataframe to store interpolated points in
interp_df = pd.DataFrame({'y':y_mesh.flatten(), 'x': x_mesh.flatten()})

# Interpolate using desired method with scipy's griddata
pm_interp = griddata((df['x'], df['y']), df['z'], (x_mesh, y_mesh), method = 'linear')
interp_df['interpolated value'] = pm_interp.flatten()

alt.Chart(interp_df).mark_rect().encode(
    x='x:O',
    y='y:O',
    color='interpolated value:Q'
)

You probably still want to mess around with the x and y-axes (switching them to :Q) does something funny (because of where the origin is I think).

visualization

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dzonimn picture dzonimn  路  3Comments

SuperShinyEyes picture SuperShinyEyes  路  3Comments

fischcheng picture fischcheng  路  4Comments

nielsmde picture nielsmde  路  4Comments

pabloinsente picture pabloinsente  路  3Comments