Baffled by numpy.unique()

Viewed 133

As an extension to my previous project, where the equation X[i+1]=R*X[i](1-X[i]) is used to demonstrate a chaotic system (depending on R). Now I'm trying to construct the bifurcation graph.

About the code, I defined the function to do the actual calculations, and extracting the last 100 calculated values (to ensure the equilibrium reached), in order to plot out the bifurcated R vs x[i], I'm appending each R value to a empty X-data list, and multiple (aka, the returned 100 values) x[i] to a Y-data list (so it is actually a nested list...)

The thing is, depending on the R value, x[i] can be either single value (after equilibrium reached) or multiple values. So I was thinking to "purify" the nested Y-data list by numpy.unique() to remove all the replicated values.

Weirdly, when I don't make the extra "purification" step, the code actually works.

But when I put x = np.unique(logistic_calc(R,N)) it throws me a error says ValueError: setting an array element with a sequence.

Below is the code that works...

import numpy as np
import matplotlib.pyplot as plt

R = 0.2
N = 10_000
x0 = 0.5

def logistic_calc(R,N):
    x = np.empty(N)
    x[0] = x0
    for i in range(1, N):
        x[i] = R* x[i-1] * (1 - x[i-1])
    return x[-100:]

x_lst = [] 
y_lst = []
for r in np.linspace(0.1,4,100):
    R = r 
    x = logistic_calc(R,N)
    x_lst.append(r)
    y_lst.append(x)

plt.figure(figsize=(7, 4))
plt.plot(x_lst, y_lst, ls='', marker='.',ms='0.5', c="royalblue")
plt.ylim(0, 1)
plt.grid(c="lightgray")
plt.xlabel(r"$r$")
plt.ylabel(r"$x_n$")

plt.show()
2 Answers

When I run your example with np.unique, I get ...

...

Traceback (most recent call last):
  File "test.py", line 29, in <module>
    plt.plot(x_lst, y_lst, ls='', marker='.',ms='0.5', c="royalblue")

... more stack trace

ValueError: setting an array element with a sequence.

So the error is clearly happening at line ...

plt.plot(x_lst, y_lst, ls='', marker='.',ms='0.5', c="royalblue")

because the shapes of x_lst and y_lst no longer match up when you use np.unique.

You can get the code to work by looping over each each index of x_lst and y_lst and plotting them separately ...

import numpy as np
import matplotlib.pyplot as plt

R = 0.2
N = 10_000
x0 = 0.5

def logistic_calc(R,N):
    x = np.empty(N)
    x[0] = x0
    for i in range(1, N):
        x[i] = R* x[i-1] * (1 - x[i-1])
    return x[-100:]

x_lst = [] 
y_lst = []
for r in np.linspace(0.1,4,100):
    R = r 
    x = logistic_calc(R,N)
    x = x.reshape(100)
    x_lst.append(r)
    y_lst.append(np.unique(x.round(decimals=4)))

plt.figure(figsize=(7, 4))
for x, y in zip(x_lst, y_lst):
    plt.plot([x]*len(y), y, ls='', marker='.',ms='0.5', c="royalblue")
plt.ylim(0, 1)
plt.grid(c="lightgray")
plt.xlabel(r"$r$")
plt.ylabel(r"$x_n$")

plt.show()

From matplotlib documentation, paragraph "Plotting multiple sets of data":

"If x and/or y are 2D arrays a separate data set will be drawn for every column. If both x and y are 2D, they must have the same shape. If only one of them is 2D with shape (N, m) the other must have length N and will be used for every data set m."

It is not explicitly written that all sublists must have the same length. But it only refers to 2D arrays and not ragged nested sequences. To understand the behavior of plt.plot, just imagine that x and y will be cast into numpy arrays. In your second case, since y_lst contains lists with different lengths, this conversion cannot be made.

So I would go for something like this:

plt.figure(figize=(7, 4))
for r in np.linspace(1, 4, 100):
    x = np.unique(logistic_calc(r, N))
    plt.plot([r], [x], '.', ms=.5, c="royalblue")  # a little bit tricky!
    # OR
    # plt.plot([r] * len(x), x, '.', ms=.5, c="royalblue")

...
plt.show()
    
Related