Search code examples
pythonpandasdictionarymatplotlib3d

Plot a dictionary entrys as 3D Plot


I have a dictionary, where each entry represents a pandas dataframe. The dictionary has 100 entrys.

The dataframes represent Absorption measurements over a given distance along the X-axis, keeping the Y-axis position fixed. Each dataframe corresponds to a different Y position. Those steps between the Y positions are fixed (100µm).

I want to plot those DataFrames next to each other, in a single plot, considering also the Y-axis. Therefore creating a 3D plot.

Up until now, I only can plot all the DataFrames in one single 2D plot.

The picture attached shows the idea. Example with only 2 dataframes


Solution

  • See the code example below, which includes some test data.

    enter image description here

    The code assumes there's a common x-axis across all Absorption entries. If each absorption list is recorded over different x, then an additional interpolation step would need to be added to map all the data onto a common axis.

    import pandas as pd
    import numpy as np
    from mpl_toolkits import mplot3d
    from matplotlib import pyplot as plt
    
    #Synthesise data
    # data_dict contains 100 dataframes, indexed data_dict[0] to data_dict[99].
    # Each dataframe has 2 columns: Absorption, and the x values at which the measurement was made
    np.random.seed(0)
    
    num_x_points = 200
    x_axis = np.linspace(0, 2*np.pi / 2, num_x_points)
    data_dict = {}
    for i in range(100):
        abs_data = (np.random.uniform(0.9, 1.1)
                    - np.cos(x_axis + np.random.uniform(0, i*np.pi/180))
                    ) / (0.1*i + 10)
        
        data_dict[i] = pd.DataFrame({
            'Absorption': abs_data,
            'x_axis': x_axis
        })
    
    #Determine number of y points
    num_y = len(data_dict)
    #Create y axis (in um)
    y_step = 100e-6
    y_axis = 1e6 * np.linspace(0, num_y * y_step, num_y)
    
    #Compile absorption lines into a single matrix
    abs_lines = np.empty((num_y, len(x_axis)))
    for y_idx, df in enumerate(data_dict.values()):
        abs_lines[y_idx, :] = df.Absorption.to_numpy()
    
    #Create a meshgrid for plotting in 3D
    grid_x, grid_y = np.meshgrid(x_axis, y_axis)
    
    #You can view the data either as lines with solid colours,
    # or as points coloured by absorption
    view_as_scatter = True
    
    #Make 3d figure
    figsize = 8
    ax = plt.figure(figsize=[figsize] * 2).add_subplot(projection='3d')
    #adjust the viewing angles
    ax.view_init(elev=20, azim=240, roll=0)
    
    if view_as_scatter:
        ax.scatter3D(grid_x, grid_y, abs_lines, c=abs_lines, marker='.', s=3, cmap='brg')
    else:
        for y_idx, df in enumerate(data_dict.values()):
            ax.plot3D(grid_x[y_idx, :], grid_y[y_idx, :], df.Absorption)
        
    ax.set_xlabel('x')
    ax.set_ylabel('y (um)')
    ax.set_zlabel('absorption')