Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heatmap smoothing/interpolation in Altair #2047

Open
firasm opened this issue Mar 28, 2020 · 3 comments
Open

Heatmap smoothing/interpolation in Altair #2047

firasm opened this issue Mar 28, 2020 · 3 comments
Labels
enhancement vega: vega-lite Requires upstream action in `vega-lite`

Comments

@firasm
Copy link

firasm commented Mar 28, 2020

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

@jakevdp
Copy link
Collaborator

jakevdp commented Mar 28, 2020

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

@jakevdp jakevdp added enhancement vega: vega-lite Requires upstream action in `vega-lite` labels Mar 28, 2020
@cgostic
Copy link

cgostic commented Mar 28, 2020

@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.

@firasm
Copy link
Author

firasm commented Mar 28, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement vega: vega-lite Requires upstream action in `vega-lite`
Projects
None yet
Development

No branches or pull requests

3 participants