Understanding the efficient way(loop) to hide all spines in a plot

Screen Link:
https://app.dataquest.io/m/147/improving-plot-aesthetics/7/hiding-spines
My Code:

ax.spines["right","left"].set_visible(False)

What I expected to happen:

I expected it to hide right and left spines

What actually happened:

      5 # Add your code here
----> 6 ax.spines["right","left"].set_visible(False)
      7 ax.legend(loc='upper right')
      8 ax.set_title('Percentage of Biology Degrees Awarded By Gender')

KeyError: ('right', 'left')

When I got the above error, I tried passing a single argument as given in the lesson. But I ended up writing four lines. As I was looking for a more efficient way to hide all spines so I decided to match my answer with DQ’s answer. As expected, I saw a more efficient answer but I didn’t understand it completely especially the reason and need behind the two iterator keys in the loop. Can someone please explain it to me. The only thing I understand is that axes contain a spines object dictionary so yes there exists a possibility to iterate over it and ax.spines.items() gives us a list of tuples(if I am not wrong).

for key,spine in ax.spines.items():
    spine.set_visible(False)

Hello @asadali_baig !
I did this mission couple of days ago and I was wondering exactly the same question.

I might certainly be wrong (and would be very interested if someone can explain it clearly) but here is how I see it:

The “ax” object contain a “spines” attribute which is a dictionary than enable us to control the different spines. In this dictionnary, there are four duo keys-values:
“right” : the “right_spine” object
“left” : the “left_spine” object"
“top” : the “top_spine” object
“bottom” : the “bottom_spine” object

Thus, when you do the following:

for key,spine in ax.spines.items():
spine.set_visible(False)

You iterate over the different values (the different spine objects above) in the spines dictionary, which enable you to hide them.

And, therefore, when you do the following:

ax.spines[“right”].set_visible(False)

You basically access to the “right_spine” object thanks to the spines dictionary and the key “right”, and use the method “set_visible” over this object to hide it.

I hope it helped you a bit and I’m waiting for someone more skilled than me to explain this thing better ! :smile:

Well as far as I can understand is that two iterators key, spine are may be due to the reason that ax.spines.items() does not give us a simple list but rather list of tuples. So, the first one serves the purpose to loop through each item in the list and the second for the tuple at the item location. But again that just what I think let’s wait for someone with more experience and grip on this topic.

It seems that the “key” in the for loop is useless, it is there only because of the ax.spines.items() returns a Dict, and we need it to store the two values. Thinking that way, the spine object sounds like a hash address pointing to something else, perhaps the spine itself, anyway, I’m looking forward to a better explanation too.

1 Like

i think i understand that the 'key, syntax is referencing the key in the ax.spines dictionary, im not sure that i get the .item() portion at the end

In simple terms, the item() method returns a tuple like this (key,value) from a dictionary.

I wrote each line separately. I don’t like to use the word, “should,” but should I be at the point where the for loop is the obvious answer?

ax.spines is a dictionary object:

print(ax.spines)
{'bottom': <matplotlib.spines.Spine object at 0x7f4d7741ab38>,
'right': <matplotlib.spines.Spine object at 0x7f4d7741a940>,
'left': <matplotlib.spines.Spine object at 0x7f4d7741a5c0>,
'top': <matplotlib.spines.Spine object at 0x7f4d7741af60>}

And when we call the .items() method of a dictionary object, it returns both keys and values as a list of tuples1 in the following format:
[(key_1, value_1), (key_2, value_2), ... , (key_n, value_n)]

1Technically, they are objects of <class 'dict_items'>, but let’s not dive into it for the sake of keeping it simple and relevant

dict_items([
('left', <matplotlib.spines.Spine object at 0x7fe2fc09c358>),
('top', <matplotlib.spines.Spine object at 0x7fe2fc09c160>),
('bottom', <matplotlib.spines.Spine object at 0x7fe2fc09c588>),
('right', <matplotlib.spines.Spine object at 0x7fe2fc09c390>)
])

If we simply loop through it, on each iteration we will be working with a tuple object and we have to manually select the spine object from it like this:

for tup in ax.spines.items():
    tup[1].set_visible(False)

There are no issues with the above approach. However, one of the features of Python is called tuple unpacking (or iterable unpacking)

Let’s say we have a tuple (or list) with two values like this:
(1, 2)

If we want to assign it to multiple variables we can simply do:
a, b = (1, 2)

The above syntax will assign 1 to a and 2 to b. This is exactly what is happening with the Dataquest solution code.

for key,spine in ax.spines.items():
    spine.set_visible(False)

ax.spines.items() will return a list of tuples. Therefore, on each iteration, we will be working with a tuple object with two values.

To capture those two values separately, we will be using 2 for-loop variables to unpack the tuple.

That being said, as we have no use for the dictionary key’s (would be useful if we only want to modify some spines), another simple solution would be to use the dictionary.values() method like this:

for spine in ax.spines.values():
    spine.set_visible(False)

Hope this helps :slightly_smiling_face:

Best,
Sahil

11 Likes

Thanks a lot for this very detailed explanation. I’m sure many others will find it very helpful.

1 Like

Hi Sahil,

Thanks for your clear explanation. Now we know ax.spines is a set of dictionary.

I used the following for loop without the methods values() or items() using the variable “key” to express the keys (“left”, “right”,"bottom, “up”) in the ax.spines dictionary and it works.

for key in ax.spines:
ax.spines[key].set_visible(False)

Are there any limitations to this form of coding that I would have to take note of even if the output is the same?

Thank you.

1 Like

Hi @jinxianlum,

That code is correct as well. By default, dictionaries are looped through their keys. Because of that ax.spines is equivalent to ax.spines.keys(). There are no limitations with that form of coding. However, if you follow the zen of python then it says Explicit is better than implicit..

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Best,
Sahil

2 Likes

This is what I did:

shape_list = ["right", "left", "top", "bottom"]
for i in shape_list:
    ax.spines[i].set_visible(False)

It works the same, but I think I would use the other method going forward.

1 Like

I used the same approach as toluthapalowo (which I recognize is not as elegant as explained in earlier comments), yet the solution checker marks it wrong even though the spines are correctly hidden on the plot and everything else looks right.
Is the solution checker looking at my code or at its result? What am I missing?? Help!

Hi @amgadsquires,

Can you please share with us the code you have used?

Thanks,
Sahil

oh, of course. Here it is:

fig, ax = plt.subplots()
ax.plot(women_degrees['Year'], women_degrees['Biology'], c='blue', label='Women')
ax.plot(women_degrees['Year'], 100-women_degrees['Biology'], c='green', label='Men')
ax.tick_params(bottom="off", top="off", left="off", right="off")
for side in ["right", "bottom", "left", "top"]:
    ax.spines[side].set_visible(False)
ax.legend(loc='upper right')
ax.set_title('Percentage of Biology Degrees Awarded By Gender')
plt.show()
1 Like

Hi @amgadsquires,

Your code is correct. There seems to be an issue in our plotting missions; even the initial code is passing the answer checker. I have reported it to our engineering team. Thank you for bringing this to our attention.

Best,
Sahil

Hello Sahil,

I’m experiencing the same issue. Any update from engineering on this please?
Thank you.

1 Like

Hi @shaun,

Sorry, this issue has not been fixed yet. I will reply to this post once it is fixed. Meanwhile, to mark the screen as completed, you can follow this workaround:

Best,
Sahil

1 Like

Hi All,

We have fixed this issue for missions 147 and 148 by commenting out the initial codes in the solution. If you are experiencing this issue in any other missions, let me know. :slightly_smiling_face:

Best,
Sahil

For what its worth, I did the same thing and have the same question. I have zero technical background and am starting from scratch here and running through the data science in python course. My approach so far has just been to fail forward, answer the best I can, get it wrong and then try to reverse engineer the answer they provide. A lot of great insights have occurred, like this one about how to iterate over dictionaries, but it is definitely frustrating feeling like I am continually being asked to perform things they have not, at least explicitly, shown how to do. I am curios though, since 5 months have passed since you wrote this comment, if you look at this page now, does the for loop seem obvious? Have you finished the course you were taking?