1

I want to have an interactive plot in jupyter (4.0.6) notebook using matplotlib (1.5.1). The thing is that the static plot is created with a function that has four variables, two of them are constants, two of them are keyword arguments, and I want to interactively change the keyword arguments.

Is this possible, and if yes, how?

The conceptual code below shows the function that generates a plot make_figure(...) and the command to generate an interactive plot.

If I change the keyword arguments to variables, then I get the error message "interact() takes from 0 to 1 positional arguments but 3 were given"

conceptual code:

def make_figure(const_1, const_2, var_1=0.4, var_2=0.8):
    b = calc_b(var_1, var_2)
    c = calc_c(b, const_1, const_2)
    fig, ax = plt.subplots()
    N, bins, patches = ax.hist(c)


interact(make_figure, 
         const_1,
         const_2,
         var_1=(0.2, 0.4, 0.05),
         var_2=(0.75, 0.95, 0.05))

addition 20160325: code example

I am trying to create a histogram for marks for a class, dependent on the percentage necessary to achieve a 1.0 and a 4.0, respectively.

# setup some marks
ids_perc = np.random.random(33) 
print("number of entered marks: ", ids_perc.shape)

the main code for the histogram; main function: get_marks

# define possible marks
marks = np.array([1.0,
                  1.3,
                  1.7,
                  2.0,
                  2.3,
                  2.7,
                  3.0,
                  3.3,
                  3.7,
                  4.0,
                  5.0])
marks_possible = marks[::-1]

def get_perc_necessary(min_perc_one,
                       min_perc_four,
                       n_marks):
    """
    calculates an equally spaced array for percentage necessary to get a mark
    """
    delta = (min_perc_one - min_perc_four)/(n_marks-2-1)
    perc_necessary_raw = np.linspace(start=min_perc_four, 
                                     stop=min_perc_one, 
                                     num=n_marks-1)
    perc_necessary = np.append([0.0], np.round(perc_necessary_raw, decimals=2)) 
    return perc_necessary


def assign_marks(n_students,
                 perc_necessary,
                 achieved_perc,
                 marks_real):
    """
    get the mark for each student (with a certain achieved percentage)
    """
    final_marks = np.empty(n_students)

    for cur_i in range(n_students):
        idx = np.argmax(np.argwhere(perc_necessary <= achieved_perc[cur_i]))
        final_marks[cur_i] = marks_real[idx]

    return final_marks


def get_marks(achieved_perc = ids_perc,
              marks_real = marks_possible,                    
              min_perc_four = 0.15,
              min_perc_one = 0.85):

    n_marks = marks.shape[0]
#     print("n_marks: ", n_marks)
    n_students = achieved_perc.shape[0]
#     print("n_students: ", n_students)

    # -----------------------------
    # linear step between each mark
    perc_necessary = get_perc_necessary(min_perc_one,
                                        min_perc_four,
                                        n_marks)

    # test query: there need to be as many percentages as marks
    if perc_necessary.shape[0] != marks_real.shape[0]:
        print("the number of marks has to be equal the number of boundaries")
        raise Exception

    # ------------
    # assign marks 
    final_marks = assign_marks(n_students,
                               perc_necessary,
                               achieved_perc,
                               marks_real)    

    # ------------
    # create table
    fig, ax = plt.subplots()
    N, bins, patches = ax.hist(final_marks, 
                               align='mid', 
                               bins=np.append(marks,6.)) # bins=marks
    ax.xaxis.set_major_formatter(FormatStrFormatter('%0.1f'))
    bin_centers = 0.5 * np.diff(bins) + bins[:-1]
    ax.set_xticks(bin_centers)
    ax.set_xticklabels( marks )
    ax.set_xlabel("mark")
    ax.set_ylabel("number of marks")
    ax.set_ylim(0.0, 6.0)
    plt.grid(True)

Now, when I try to setup interact doing this

interact(get_marks, 
     min_perc_four=(0.2, 0.4, 0.05),
     min_perc_one=(0.75, 0.95, 0.05));

I get the error

ValueError: array([ 0.22366653,  0.74206953,  0.47501716,  0.56536227,  0.54792759,
    0.60288287,  0.68548973,  0.576935  ,  0.84582243,  0.40709693,
    0.78600622,  0.2692508 ,  0.62524819,  0.62204851,  0.5421716 ,
    0.71836192,  0.97194698,  0.4054752 ,  0.2185643 ,  0.11786751,
    0.57947848,  0.88659768,  0.38803576,  0.66617254,  0.77663263,
    0.94364543,  0.23021637,  0.30899724,  0.08695842,  0.50296694,
    0.8164095 ,  0.77892531,  0.5542163 ]) cannot be transformed to a Widget

Why is this error looking at the variable ids_perc?

1 Answer 1

2

You need to assign your variables explicitly in interact(). For example, like this:

const_1 = 1

interact(make_figure, 
         const_1=const_1,
         const_2=2,
         var_1=(0.2, 0.4, 0.05),
         var_2=(0.75, 0.95, 0.05))

Or (if possible) change the signature of make_figure to make those variables into keyword arguments, so that you can avoid passing them explicitly:

def make_figure(const_1=1, const_2=2, var_1=0.4, var_2=0.8):
    ....

interact(make_figure, 
         var_1=(0.2, 0.4, 0.05),
         var_2=(0.75, 0.95, 0.05))

Here is MCWE that you can try:

def calc_b(v1, v2):
    return v1 + v2

def calc_c(v1, v2, v3):
    return [v1, v2, v3]

def make_figure(const_1=1, const_2=2, var_1=0.4, var_2=0.8):
    b = calc_b(var_1, var_2)
    c = calc_c(b, const_1, const_2)
    fig, ax = plt.subplots()
    N, bins, patches = ax.hist(c)

interact(make_figure, 
         var_1=(0.2, 0.4, 0.05),
         var_2=(0.75, 0.95, 0.05));

This runs without any errors.

On your addition 20160325:

Every parameter you pass to interact will have to be representable by either one of (simplifying it somewhat):

  • a slider (for tuples, that represent (min, max) and scalar numbers)
  • selection box (for lists of strings and dictionaries)
  • check box (for booleans)
  • an input box (for strings)

You are passing (implicitly by defining in your get_marks two parameters as np.arrays). So interact doesn't know how to represent that on a slider, hense the error.

You have at least two options:

1) to change the signature of the get_marks so that it takes parameters that interact will undetstand (see bullet list above)

2) make another wrapper function that will take parameters that interact undetstands, but will call get_marks after converting those parameters to whatever get_marks needs.

So just one extra step and you're done. ;-)

UPDATE:

Here is your code with wrapper that works for me. Note that get_marks_interact does not need to take all the params of get_marks and I don't pass lists as interact will have a problem with them (list should represent either a list of strings (for Dropdown Widgets) or list/tuple of [min, max] values (for slider)).

def get_marks(min_perc_four = 0.15,
              min_perc_one = 0.85,
              marks=marks_possible,
              ach_per=ids_perc):

    marks_real = marks #  [0]
    achieved_perc = ach_per #  [0]

    n_marks = marks_real.shape[0]
    print("n_marks: ", n_marks)
    n_students = achieved_perc.shape[0]
    print("n_students: ", n_students)

    # -----------------------------
    # linear step between each mark
    perc_necessary = get_perc_necessary(min_perc_one,
                                        min_perc_four,
                                        n_marks)

    # test query: there need to be as many percentages as marks
    if perc_necessary.shape[0] != marks_real.shape[0]:
        print("the number of marks has to be equal the number of boundaries")
        raise Exception

    # ------------
    # assign marks 
    final_marks = assign_marks(n_students,
                               perc_necessary,
                               achieved_perc,
                               marks_real)

    # ------------
    # create table
    fig, ax = plt.subplots()
    N, bins, patches = ax.hist(final_marks, 
                               align='mid', 
                               bins=np.sort(np.append(marks, 6.))) # bins=marks
    ax.xaxis.set_major_formatter(FormatStrFormatter('%0.1f'))
    bin_centers = 0.5 * np.diff(bins) + bins[:-1]
    ax.set_xticks(bin_centers)
    ax.set_xticklabels( marks )
    ax.set_xlabel("mark")
    ax.set_ylabel("number of marks")
    ax.set_ylim(0.0, 6.0)
    plt.grid(True)

def get_marks_interact(min_perc_four = 0.15, 
                       min_perc_one = 0.85,):
    return get_marks(min_perc_four, min_perc_one)

interact(get_marks_wrapper, 
         min_perc_four=(0.2, 0.4, 0.05),
         min_perc_one=(0.75, 0.95, 0.05));
Sign up to request clarification or add additional context in comments.

7 Comments

I changed make_figure to have only keyword arguments in its definition, and in interact, I define the range only for the variables whose value I want to change (like in your second code block). Now I get the error ValueError: array of <'const_1'> cannot be transformed to a Widget Does interact work automatically only on the matplotlib figure defined in the calling function? I.e. it does not matter that a variety of stuff is being calculated in make_figure (like b and c)?
I have added a minimal example. Your current execution of make_figure creates new figure every time a parameter is changed in interact. So unless you provide a reproducible example to illustrate an issue trying to debug it is a guess game.
with your minimal example, it works. However, I am still stuck with my original problem. Hence, I added a little bit longer example, including the error message. Why is this error looking at the variable ids_perc when I really want to interact with the figure?
I have updated the answer to help you with this error.
thanks for your efforts. However: If I choose option 1, and look at your bulleted list, I can see the only option is to put my numpy arrays into a dictionary (so to say a dictionary for the constants). If I do this I get ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all(). I am uncertain where this is coming from, as the only query is the np.argwhere statement in assign_marks combined with an np.argmax(), and there is no boolean query. get_marks() runs still fine, with the dictionary but without interact.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.