Search code examples
pythondictionarymatplotlibplotgenerator

Python 3 matplotlib does not support generators as input


I am fairly new to Python and it took me hours to figure out what I can do with no success. I am trying an example in plotting from https://jupyter.brynmawr.edu/services/public/dblank/jupyter.cs/FLAIRS-2015/TSPv3.ipynb#Python-and-iPython-Notebook:-Preliminaries. In the section on "Plotting Tours", as labeled as cell 15,16,17,18 and maybe more. The function for plot_tour and plot_lines are using map() and I read that in Python 3 it's a generator instead of a list. This made it confusing so is there any way around this to output the plot? which possibly not using map(). Thank you so much in advance.

First plot code:

def plot_tour(tour): 
    "Plot the cities as circles and the tour as lines between them."
    plot_lines(list(tour) + [tour[0]])
    
def plot_lines(points, style='bo-'):
    "Plot lines to connect a series of points."
    plt.plot(map(X, points), map(Y, points), style)
    plt.axis('scaled'); plt.axis('off')
plot_tour(alltours_tsp(Cities(8)))

This is the error im getting:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Input In [18], in <cell line: 1>()
----> 1 plot_tour(alltours_tsp(Cities(8)))

Input In [17], in plot_tour(tour)
      1 def plot_tour(tour): 
      2     "Plot the cities as circles and the tour as lines between them."
----> 3     plot_lines(list(tour) + [tour[0]])

Input In [17], in plot_lines(points, style)
      5 def plot_lines(points, style='bo-'):
      6     "Plot lines to connect a series of points."
----> 7     plt.plot(map(X, points), map(Y, points), style)
      8     plt.axis('scaled'); plt.axis('off')

File ~\anaconda3\lib\site-packages\matplotlib\pyplot.py:2757, in plot(scalex, scaley, data, *args, **kwargs)
   2755 @_copy_docstring_and_deprecators(Axes.plot)
   2756 def plot(*args, scalex=True, scaley=True, data=None, **kwargs):
-> 2757     return gca().plot(
   2758         *args, scalex=scalex, scaley=scaley,
   2759         **({"data": data} if data is not None else {}), **kwargs)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_axes.py:1632, in Axes.plot(self, scalex, scaley, data, *args, **kwargs)
   1390 """
   1391 Plot y versus x as lines and/or markers.
   1392 
   (...)
   1629 (``'green'``) or hex strings (``'#008000'``).
   1630 """
   1631 kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
-> 1632 lines = [*self._get_lines(*args, data=data, **kwargs)]
   1633 for line in lines:
   1634     self.add_line(line)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_base.py:312, in _process_plot_var_args.__call__(self, data, *args, **kwargs)
    310     this += args[0],
    311     args = args[1:]
--> 312 yield from self._plot_args(this, kwargs)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_base.py:493, in _process_plot_var_args._plot_args(self, tup, kwargs, return_kwargs)
    490     x, y = index_of(xy[-1])
    492 if self.axes.xaxis is not None:
--> 493     self.axes.xaxis.update_units(x)
    494 if self.axes.yaxis is not None:
    495     self.axes.yaxis.update_units(y)

File ~\anaconda3\lib\site-packages\matplotlib\axis.py:1443, in Axis.update_units(self, data)
   1437 def update_units(self, data):
   1438     """
   1439     Introspect *data* for units converter and update the
   1440     axis.converter instance if necessary. Return *True*
   1441     if *data* is registered for unit conversion.
   1442     """
-> 1443     converter = munits.registry.get_converter(data)
   1444     if converter is None:
   1445         return False

File ~\anaconda3\lib\site-packages\matplotlib\units.py:206, in Registry.get_converter(self, x)
    202 else:
    203     # ... and avoid infinite recursion for pathological iterables for
    204     # which indexing returns instances of the same iterable class.
    205     if type(first) is not type(x):
--> 206         return self.get_converter(first)
    207 return None

File ~\anaconda3\lib\site-packages\matplotlib\units.py:199, in Registry.get_converter(self, x)
    197         pass
    198 try:  # If cache lookup fails, look up based on first element...
--> 199     first = cbook.safe_first_element(x)
    200 except (TypeError, StopIteration):
    201     pass

File ~\anaconda3\lib\site-packages\matplotlib\cbook\__init__.py:1678, in safe_first_element(obj)
   1676     except TypeError:
   1677         pass
-> 1678     raise RuntimeError("matplotlib does not support generators "
   1679                        "as input")
   1680 return next(iter(obj))

RuntimeError: matplotlib does not support generators as input

Next plot code:

def plot_tsp(algorithm, cities):
    "Apply a TSP algorithm to cities, plot the resulting tour, and print information."
    # Find the solution and time how long it takes
    t0 = time.clock()
    tour = algorithm(cities)
    t1 = time.clock()
    assert valid_tour(tour, cities)
    plot_tour(tour); plt.show()
    print("{} city tour with length {:.1f} in {:.3f} secs for {}"
          .format(len(tour), tour_length(tour), t1 - t0, algorithm.__name__))
    
def valid_tour(tour, cities):
    "Is tour a valid tour for these cities?"
    return set(tour) == set(cities) and len(tour) == len(cities)
plot_tsp(alltours_tsp, Cities(8))

This is the error im getting:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Input In [23], in <cell line: 1>()
----> 1 plot_tsp(alltours_tsp, Cities(8))

Input In [22], in plot_tsp(algorithm, cities)
      6 t1 = time.perf_counter()
      7 assert valid_tour(tour, cities)
----> 8 plot_tour(tour); plt.show()
      9 print("{} city tour with length {:.1f} in {:.3f} secs for {}"
     10       .format(len(tour), tour_length(tour), t1 - t0, algorithm.__name__))

Input In [17], in plot_tour(tour)
      1 def plot_tour(tour): 
      2     "Plot the cities as circles and the tour as lines between them."
----> 3     plot_lines(list(tour) + [tour[0]])

Input In [17], in plot_lines(points, style)
      5 def plot_lines(points, style='bo-'):
      6     "Plot lines to connect a series of points."
----> 7     plt.plot(map(X, points), map(Y, points), style)
      8     plt.axis('scaled'); plt.axis('off')

File ~\anaconda3\lib\site-packages\matplotlib\pyplot.py:2757, in plot(scalex, scaley, data, *args, **kwargs)
   2755 @_copy_docstring_and_deprecators(Axes.plot)
   2756 def plot(*args, scalex=True, scaley=True, data=None, **kwargs):
-> 2757     return gca().plot(
   2758         *args, scalex=scalex, scaley=scaley,
   2759         **({"data": data} if data is not None else {}), **kwargs)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_axes.py:1632, in Axes.plot(self, scalex, scaley, data, *args, **kwargs)
   1390 """
   1391 Plot y versus x as lines and/or markers.
   1392 
   (...)
   1629 (``'green'``) or hex strings (``'#008000'``).
   1630 """
   1631 kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
-> 1632 lines = [*self._get_lines(*args, data=data, **kwargs)]
   1633 for line in lines:
   1634     self.add_line(line)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_base.py:312, in _process_plot_var_args.__call__(self, data, *args, **kwargs)
    310     this += args[0],
    311     args = args[1:]
--> 312 yield from self._plot_args(this, kwargs)

File ~\anaconda3\lib\site-packages\matplotlib\axes\_base.py:493, in _process_plot_var_args._plot_args(self, tup, kwargs, return_kwargs)
    490     x, y = index_of(xy[-1])
    492 if self.axes.xaxis is not None:
--> 493     self.axes.xaxis.update_units(x)
    494 if self.axes.yaxis is not None:
    495     self.axes.yaxis.update_units(y)

File ~\anaconda3\lib\site-packages\matplotlib\axis.py:1443, in Axis.update_units(self, data)
   1437 def update_units(self, data):
   1438     """
   1439     Introspect *data* for units converter and update the
   1440     axis.converter instance if necessary. Return *True*
   1441     if *data* is registered for unit conversion.
   1442     """
-> 1443     converter = munits.registry.get_converter(data)
   1444     if converter is None:
   1445         return False

File ~\anaconda3\lib\site-packages\matplotlib\units.py:206, in Registry.get_converter(self, x)
    202 else:
    203     # ... and avoid infinite recursion for pathological iterables for
    204     # which indexing returns instances of the same iterable class.
    205     if type(first) is not type(x):
--> 206         return self.get_converter(first)
    207 return None

File ~\anaconda3\lib\site-packages\matplotlib\units.py:199, in Registry.get_converter(self, x)
    197         pass
    198 try:  # If cache lookup fails, look up based on first element...
--> 199     first = cbook.safe_first_element(x)
    200 except (TypeError, StopIteration):
    201     pass

File ~\anaconda3\lib\site-packages\matplotlib\cbook\__init__.py:1678, in safe_first_element(obj)
   1676     except TypeError:
   1677         pass
-> 1678     raise RuntimeError("matplotlib does not support generators "
   1679                        "as input")
   1680 return next(iter(obj))

RuntimeError: matplotlib does not support generators as input

Solution

  • I suspect this notebook was made for an earlier version of python and packages. That's why you're having so many problems. I recommend tracking down some specifications of what python and package versions were used in the course. However, I did get a lot of the notebook to run with these changes:

    def plot_lines(points, style='bo-'):
        "Plot lines to connect a series of points."
        plt.plot(list(map(X, points)), list(map(Y, points)), style)
        plt.axis('scaled'); plt.axis('off')
    

    Later on, time.time() should be replaced with perf_counter().

    def plot_tsp(algorithm, cities):
        "Apply a TSP algorithm to cities, plot the resulting tour, and print information."
        # Find the solution and time how long it takes
        t0 = time.perf_counter()
        tour = algorithm(cities)
        t1 = time.perf_counter()
        assert valid_tour(tour, cities)
        plot_tour(tour); plt.show()
        print("{} city tour with length {:.1f} in {:.3f} secs for {}"
              .format(len(tour), tour_length(tour), t1 - t0, algorithm.__name__))
    
    def benchmark(function, inputs):
        "Run function on all the inputs; return pair of (average_time_taken, results)."
        t0           = time.perf_counter()
        results      = map(function, inputs)
        t1           = time.perf_counter()
        average_time = (t1 - t0) / len(inputs)
        return (average_time, results)
    

    There are other changes that have to be made further down the notebook, however. I had problems with the benchmark function and the USA big map url request. You should really consider digging the package versions though.