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

    pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy