3

I have many 2d sequences with variable length, i.e. lists of list where each sublist is a sequence. I want to project these sequences/lines/sublists in a 3d visualisation adding time-step as another dimension. So far I am failing to plot all the 3d lines using plotly.express.

import plotly.express as px

t = [[ii+1 for ii in range(len(features[i]))] for i in range(len(labels))]
x0 = [[x[0] for x in features[i]] for i in range(len(labels))]
x1 = [[x[1] for x in features[i]] for i in range(len(labels))]

df = pd.DataFrame(dict(
    X=[tii for ti in t for tii in ti],
    Y=[xii for xi in x0 for xii in xi],
    Z=[xii for xi in x1 for xii in xi],
    color=[aa for a in labels for aa in a]
))
fig = px.line_3d(df, x="X", y="Y", z="Z", color="color")
fig.show

This is what I get, which is not really what I want. It is treating all the cases/sublists with common label as one single sequence, thus we see at the end of each line it goes back the where it starts. I have looked up on how to iteratively plotting this in a for-loop (just like matplotlib) (basically creating a new pandas dataframe at each iteration and plot it), however with no success. Does anyone have any experience on this please? Much appreciated!

screenshot

A mcve is as below:

import plotly.express as px
import numpy as np
import pandas as pd

features = [np.random.rand(4,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(6,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(9,2).tolist()]
labels = [[1, 1, 1, 1], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2],
         [2, 2, 2, 2, 2], [0, 0, 0, 0, 0, 0, 0, 0, 0]]

t = [[ii+1 for ii in range(len(features[i]))] for i in range(len(labels))]
x0 = [[x[0] for x in features[i]] for i in range(len(labels))]
x1 = [[x[1] for x in features[i]] for i in range(len(labels))]

df2 = pd.DataFrame(dict(
    X=[tii for ti in t for tii in ti],
    Y=[xii for xi in x0 for xii in xi],
    Z=[xii for xi in x1 for xii in xi],
    color=[aa for a in labels for aa in a]
))
fig1 = px.line_3d(df2, x="X", y="Y", z="Z", color="color")
fig1.show()

You see basically 3 lines instead of 5.

3
  • 1
    It doesn't seem to me that features is defines. Do you mind to produce a mcve? Commented Feb 10, 2020 at 15:25
  • 1
    Thanks. I have provided an example in the question. Commented Feb 10, 2020 at 15:47
  • 1
    You see three lines cos for px the first set of labels [1,1,...] is the same as the second. Commented Feb 10, 2020 at 17:31

1 Answer 1

8

Your problems is that you are using the same label for different traces. Here a workaround with a loop

import numpy as np
import plotly.graph_objs as go

features = [np.random.rand(4,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(6,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(9,2).tolist()]
labels = [[1, 1, 1, 1],
          [1, 1, 1, 1, 1],
          [2, 2, 2, 2, 2, 2],
          [2, 2, 2, 2, 2],
          [0, 0, 0, 0, 0, 0, 0, 0, 0]]

fig = go.Figure()
for i, feat in enumerate(features):
    feat = np.array(feat)
    fig.add_trace(
        go.Scatter3d(
            x=np.arange(len(feat)),
            y=feat[:,0],
            z=feat[:,1],
            mode='lines',
            hovertext=labels[i]
        )
    )
fig.show()

You might need to play with trace names.

Update

Hoefully it's not too overcomplicated but it is meant to be as generic as possible


import numpy as np
import plotly.graph_objs as go
from itertools import cycle

def plotly_color_map(names):
    # From https://stackoverflow.com/a/44727682
    plotly_colors = cycle(['#1f77b4',  # muted blue
                           '#ff7f0e',  # safety orange
                           '#2ca02c',  # cooked asparagus green
                           '#d62728',  # brick red
                           '#9467bd',  # muted purple
                           '#8c564b',  # chestnut brown
                           '#e377c2',  # raspberry yogurt pink
                           '#7f7f7f',  # middle gray
                           '#bcbd22',  # curry yellow-green
                           '#17becf'  # blue-teal
                           ])

    return dict(zip(names, plotly_colors))


features = [np.random.rand(4,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(6,2).tolist(), 
            np.random.rand(5,2).tolist(), 
            np.random.rand(9,2).tolist()]

labels = [[1, 1, 1, 1],
          [1, 1, 1, 1, 1],
          [2, 2, 2, 2, 2, 2],
          [2, 2, 2, 2, 2],
          [0, 0, 0, 0, 0, 0, 0, 0, 0]]

legend_groups = [l[0] for l in labels]

traces = [False if (len(legend_groups[:i])>0 and l in legend_groups[:i]) 
          else True for i, l in enumerate(legend_groups)]

cm = plotly_color_map(set(legend_groups))

fig = go.Figure()
for i, feat in enumerate(features):
    feat = np.array(feat)
    fig.add_trace(
        go.Scatter3d(
            x=np.arange(len(feat)),
            y=feat[:,0],
            z=feat[:,1],
            mode='lines',
            line={"color":cm[legend_groups[i]]},
            legendgroup=legend_groups[i],
            hovertext=labels[i],
            showlegend=traces[i],
            name="label_{}".format(legend_groups[i])
        )
    )
fig.show()
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks very much @rpanai! May I pls ask if I can assign a colour to each line based on its label (i.e. '0' or '1' or '2'), instead of a different colour for each line? And thus the legend shows which colour indicates which label.
It'd be great if you can @rpanai! Much appreciated! Thanks!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.