Skip to content

control.timeresp.TimeResponseData.to_pandas() failing #1087

@joaoantoniocardoso

Description

@joaoantoniocardoso

Hi, today I was using some step responses and noticed that the .to_pandas() is not actually working.

I managed to workaround it by creating my own function to translate the response into a dataframe.

Example of code not working:

import control as ct
import numpy as np

model = ct.rss(states=['x0', 'x1'], outputs=['y0', 'y1'], inputs=['u0', 'u1'], name='My Model')

T = np.linspace(0, 10, 100, endpoint=False)
X0 = np.zeros(model.nstates)

res = ct.step_response(model, T=T, X0=X0, input=0)

df = res.to_pandas()

Error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[140], line 6
      3 T = np.linspace(0, 10, 100, endpoint=False)
      4 X0 = np.zeros(model.nstates)
----> 6 res = ct.step_response(model, T=T, X0=X0, input=0).to_pandas()

File ~.env/lib/python3.10/site-packages/control/timeresp.py:723, in TimeResponseData.to_pandas(self)
    719 if self.nstates > 0:
    720     data.update(
    721         {name: self.x[i] for i, name in enumerate(self.state_labels)})
--> 723 return pandas.DataFrame(data)

File ~.env/lib/python3.10/site-packages/pandas/core/frame.py:778, in DataFrame.__init__(self, data, index, columns, dtype, copy)
    772     mgr = self._init_mgr(
    773         data, axes={"index": index, "columns": columns}, dtype=dtype, copy=copy
    774     )
    776 elif isinstance(data, dict):
    777     # GH#38939 de facto copy defaults to False only in non-dict cases
--> 778     mgr = dict_to_mgr(data, index, columns, dtype=dtype, copy=copy, typ=manager)
    779 elif isinstance(data, ma.MaskedArray):
    780     from numpy.ma import mrecords

File ~.env/lib/python3.10/site-packages/pandas/core/internals/construction.py:503, in dict_to_mgr(data, index, columns, dtype, typ, copy)
    499     else:
    500         # dtype check to exclude e.g. range objects, scalars
    501         arrays = [x.copy() if hasattr(x, "dtype") else x for x in arrays]
--> 503 return arrays_to_mgr(arrays, columns, index, dtype=dtype, typ=typ, consolidate=copy)

File ~.env/lib/python3.10/site-packages/pandas/core/internals/construction.py:114, in arrays_to_mgr(arrays, columns, index, dtype, verify_integrity, typ, consolidate)
    111 if verify_integrity:
    112     # figure out the index, if necessary
    113     if index is None:
--> 114         index = _extract_index(arrays)
    115     else:
    116         index = ensure_index(index)

File ~.env/lib/python3.10/site-packages/pandas/core/internals/construction.py:664, in _extract_index(data)
    662         raw_lengths.append(len(val))
    663     elif isinstance(val, np.ndarray) and val.ndim > 1:
--> 664         raise ValueError("Per-column arrays must each be 1-dimensional")
    666 if not indexes and not raw_lengths:
    667     raise ValueError("If using all scalar values, you must pass an index")

ValueError: Per-column arrays must each be 1-dimensional

The code I'm using to workaround it is the following:

import matplotlib.pyplot as plt
import control as ct
import numpy as np

def step_response_to_pandas(step_response):
    return pd.DataFrame(
        {'trace_label': np.array([[label] * (len(res.time)) for label in res.trace_labels]).ravel()} |
        {'time': res.time.repeat(len(res.trace_labels))} |
        {label: res.inputs[i].ravel() for i,label in enumerate(res.input_labels)} |
        {label: res.outputs[i].ravel() for i,label in enumerate(res.output_labels)} |
        {label: res.states[i].ravel() for i,label in enumerate(res.state_labels)}
    )

def plot_step_response_dataframe(df):
    grouped = df.groupby(level='trace_label')
    row_size = 1

    for trace_label, group in grouped:
        fig, axes = plt.subplots(len(group.columns), 1, figsize=(6.4, len(group.columns) * row_size), sharex=True)
        fig.suptitle(f'Trace: {trace_label}', fontsize=16)
        
        if len(group.columns) == 1:
            axes = [axes]
        
        for ax, (signal_name, signal_data) in zip(axes, group.items()):
            ax.plot(group.index.get_level_values('time'), signal_data, label=signal_name)
            ax.grid(True)
            ax.set_ylabel(signal_name)
        
        axes[-1].set_xlabel('Time')
        
        plt.tight_layout()
        plt.show()


model = ct.rss(states=['x0', 'x1'], outputs=['y0', 'y1'], inputs=['u0', 'u1'], name='My Model')

T = np.linspace(0, 10, 100, endpoint=False)
X0 = np.zeros(model.nstates)

res = ct.step_response(model, T=T, X0=X0)

df = step_response_to_pandas(res)
df = df.set_index(['trace_label', 'time'])

plot_step_response_dataframe(df)

display(df)

Example of output:

image
image
image


Thanks!

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions