0

I want to use a list entry consisting of appearance commands in matplotlib for convenient editing of the line color and type. But when I run the following code I get 2 errors:

  1. 'type object got multiple values for keyword argument 'ax''
Traceback (most recent call last):
  File "./plotMeltFront.py", line 34, in <module>
    data.plot(0, 1, ax=ax1, label=myLabel, *sets[i][2].split())
  File "/home/ksalscheider/.local/lib/python3.6/site-packages/pandas/plotting/_core.py", line 872, in __call__
    plot_backend.__name__, self._parent, args, kwargs
  File "/home/ksalscheider/.local/lib/python3.6/site-packages/pandas/plotting/_core.py", line 859, in _get_call_args
    kwargs = dict(arg_def, **pos_args, **kwargs)
TypeError: type object got multiple values for keyword argument 'ax'
  1. On deleting the ax argument the error says 'color="C0" is not a valid plot kind'
Traceback (most recent call last):
  File "./plotMeltFront.py", line 34, in <module>
    data.plot(0, 1, label=myLabel, *sets[i][2].split())
  File "/home/ksalscheider/.local/lib/python3.6/site-packages/pandas/plotting/_core.py", line 882, in __call__
    raise ValueError(f"{kind} is not a valid plot kind")
ValueError: color="C0" is not a valid plot kind

I don't understand either of them. For 1 I didn't declare a second ax value in the lists, why does it say so? Error 2 occurs too, when writing data.plot(0, 1, label=myLabel, 'color="C0"') (watch the ' around color). This mimics the way the list entries look, wen the list is printed. Is there a way to strip the ' from the split list entries?

I included the script with a few example sets in the link python script with example data

#!/usr/bin/python3

import os
import matplotlib.pyplot as plt
import pandas as pd

sets = []
iOld = -1

#--------USER EDIT--------#

#["set directory name", "label", 'set appearence']
sets.append(["set1", "Experiment", 'color="C0" linestyle=":"'])
sets.append(["set2", "Linear", 'color="C1" linestyle="--"'])
sets.append(["set3", "Erf", 'color="C2" linestyle="-."'])

print(sets[0][2].split())

#------USER EDIT END------'

fig = plt.figure()
ax1 = fig.add_subplot()

for i in range(len(sets)):
    for j in os.listdir(sets[i][0]):
        filepath = os.path.abspath(sets[i][0]) + "/" + j
        data = pd.read_csv(filepath, delimiter=',', engine='python',
               header=None, skipfooter=1, error_bad_lines=False)
        if i != iOld:
            myLabel = sets[i][1];
        else:
            myLabel = '_nolegend_'

        data.plot(0, 1, ax=ax1, label=myLabel, *sets[i][2].split())
        iOld = i

plt.show()

PS: The "nolegend" entry throws a lot of warnings, is there a way to disable them?

Skaiwalker
  • 15
  • 5
  • Please include all error messages as text in the question. – Thierry Lathuille Dec 11 '20 at 17:37
  • 1
    Your `sets[i][2]` should be a dictionary, e.g. `["set1", "Experiment", {'color':"C0", 'linestyle': ":"}]`, after which you can use the dictionary via double asterisk: `data.plot(..., **sets[i][2]`. See also [What does ** (double star/asterisk) and * (star/asterisk) do for parameters?](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters). – JohanC Dec 11 '20 at 21:07

1 Answers1

1

To provide extra parameters to a function, you can't use a split string. In the example of the question, the split string would appear as:

# data.plot(0, 1, ax=ax1, label=myLabel, *sets[i][2].split())
data.plot(0, 1, ax=ax1, label=myLabel, 'color="C0"', 'linestyle=":"')

which isn't valid Python code. Python doesn't interpret it as parametername=value but just as a string. As pandas does some rearranging of the parameters, a rather unhelpful error message is generated (seemingly interpreting it as ax='color="C0"').

The way to go is to store these values in a dictionary which can be converted to parameter=value pairs via **:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

sets = []
sets.append(["set1", "Experiment", {"color": "C0", "linestyle": ":"}])
sets.append(["set2", "Linear", {"color": "C1", "linestyle": "--"}])
sets.append(["set3", "Erf", {"color": "C2", "linestyle": "-."}])

fig = plt.figure()
ax1 = fig.add_subplot()

iOld = None
for i in range(len(sets)):
    for j in range(2):
        data = pd.DataFrame({'x': np.arange(100), 'y': np.random.randn(100).cumsum()})
        myLabel = sets[i][1] if i != iOld else '_nolegend_'
        legend_dict = {'legend': i != iOld}
        data.plot(0, 1, ax=ax1, label=myLabel, **sets[i][2], **legend_dict)
        iOld = i
plt.show()

example plot

The annoying warning about the _nolegend_ is a consequence of how pandas calls the matplotlib legend function. In this case, an approach is to add legend=False for the plots that shouldn't be present in the legend.

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Thank you very much for your reply, it works very well but the legend isn't correct in my case :( When I run your script, the first 2 entries in the legend are the same color and dash pattern, the third is displayed correctly. When I load my files the legend shows all sets in the same manor, even though the lines are displayed correctly.This occurs with matplotlib version 3.3.3 both on python 3 and 3.8. Any thoughts on this? – Skaiwalker Dec 12 '20 at 14:38
  • 1
    I upgrade my libraries, and noted the same problem. Updating the code to `myLabel = sets[i][1] if i != iOld else '_nolegend_'` (and maintaining the `legend=False` to suppress the warning) seems to solve the issues. – JohanC Dec 12 '20 at 15:27