3

I am trying to plot mammalian feeding data on time points on a polar plot. In the example below, there is only one day, but each day will eventually be plotted on the same graph (via different axes). I currently have all of the aesthetics worked out, but my data is not graphing correctly. How do I get the hours to plot correctly?

I assume that the solution will likely have to do with pd.datetime and np.deg2rad, but I have not found the correct combo.

I am importing my data from csv, and filtering each day based on the date as follows:

#Filtered portion:
Day1 = df[df.Day == '5/22']

This gives me the following data:

    Day   Time  Feeding_Quality    Feed_Num
0  5/22  16:15            G        2
1  5/22  19:50            G        2
2  5/22  20:15            G        2
3  5/22  21:00            F        1
4  5/22  23:30            G        2

Here is the code:

fig = plt.figure(figsize=(7,7))

ax = plt.subplot(111, projection = 'polar')

ax.bar(Day1['Time'], Day1['Feed_Num'], width = 0.1, alpha=0.3, color='red', label='Day 1')

# Make the labels go clockwise
ax.set_theta_direction(-1)

#Place Zero at Top
ax.set_theta_offset(np.pi/2)

#Set the circumference ticks
ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False))

# set the label names
ticks = ['12 AM', '1 AM', '2 AM', '3 AM', '4 AM', '5 AM', '6 AM', '7 AM','8 AM','9 AM','10 AM','11 AM','12 PM', '1 PM', '2 PM', '3 PM', '4 PM',  '5 PM', '6 PM', '7 PM', '8 PM', '9 PM', '10 PM', '11 PM' ]
ax.set_xticklabels(ticks)

# suppress the radial labels
plt.setp(ax.get_yticklabels(), visible=False)

#Bars to the wall
plt.ylim(0,2)

plt.legend(bbox_to_anchor=(1,0), fancybox=True, shadow=True)
plt.show()

As you can assume from the data, all bars plotted would be in the afternoon, but as you can see from the graph output, the data is all over the place.

polar clock plot

dancassin
  • 77
  • 1
  • 6
  • Where in the code do you use `np.deg2rad`? Seems there isn't even any degrees present? – ImportanceOfBeingErnest Jun 02 '19 at 19:09
  • You are correct. I have not used it yet, but I assume I need to and that's where the problem is. All of the code posted is all of the code that I have used to far. I've tried to use np.deg2rad on the Time data column as follows: np.deg2rad(Day1['Time']), I get the following: AttributeError: 'str' object has no attribute 'deg2rad' – dancassin Jun 02 '19 at 23:16
  • @dancassin Could you please show how would you add more days to the graph? – Alex Davies Oct 29 '20 at 03:42
  • @AlexDavies For this specific project, I just plotted a second set of data as a different color. Adjust the alpha value to view the overlaps. – dancassin Oct 30 '20 at 16:08

1 Answers1

4
import numpy as np
from matplotlib import pyplot as plt
import datetime
df = pd.DataFrame({'Day': {0: '5/22', 1: '5/22', 2: '5/22', 3: '5/22', 4: '5/22'},
                   'Time': {0: '16:15', 1: '19:50', 2: '20:15', 3: '21:00', 4: '23:30'},
                   'Feeding_Quality': {0: 'G', 1: 'G', 2: 'G', 3: 'F', 4: 'G'},
                   'Feed_Num': {0: 2, 1: 2, 2: 2, 3: 1, 4: 2}})

Create a series of datetime.datetime objects from the 'Time' column; transform that into percentages of 24 hours; transform that into radians.

xs = pd.to_datetime(df['Time'],format= '%H:%M' )
xs = xs - datetime.datetime.strptime('00:00:00', '%H:%M:%S')
xs = xs.dt.seconds / (24 * 3600)
xs = xs * 2 * np.pi

Use that as the x values for the plot

fig = plt.figure(figsize=(7,7))
ax = plt.subplot(111, projection = 'polar')
ax.bar(xs, df['Feed_Num'], width = 0.1, alpha=0.3, color='red', label='Day 1')

# Make the labels go clockwise
ax.set_theta_direction(-1)

#Place Zero at Top
ax.set_theta_offset(np.pi/2)

#Set the circumference ticks
ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False))

# set the label names
ticks = ['12 AM', '1 AM', '2 AM', '3 AM', '4 AM', '5 AM', '6 AM', '7 AM','8 AM','9 AM','10 AM','11 AM','12 PM', '1 PM', '2 PM', '3 PM', '4 PM',  '5 PM', '6 PM', '7 PM', '8 PM', '9 PM', '10 PM', '11 PM' ]
ax.set_xticklabels(ticks)

# suppress the radial labels
plt.setp(ax.get_yticklabels(), visible=False)

#Bars to the wall
plt.ylim(0,2)

plt.legend(bbox_to_anchor=(1,0), fancybox=True, shadow=True)
plt.show()

The 'Time' column could also be transformed to radians with

def trans(x):
    h,m = map(int,x)
    return 2 * np.pi * (h + m/60)/24

xs = df['Time'].str.split(':')
xs = xs.apply(trans)

Which is probably a little better than using timedelta's - that seemed a little convoluted.

wwii
  • 23,232
  • 7
  • 37
  • 77
  • Thank you! For the 'xs' lines, I understand what the first, third, and fourth lines are doing. What is the xs = xs - datetime.datetime.strptime('00:00:00', '%H:%M:%S') doing? Looks like subtracting date time from xs? – dancassin Jun 03 '19 at 01:08
  • 1
    It makes `xs` a Series of datetime.timedelta objects to *get* seconds from midnight. – wwii Jun 03 '19 at 02:05
  • @wwii This might be a little bit late, but do you have any idea how we can add more days on the plot? E.g. day one will be the closest to the center and then as number of days increases the further it gets from the center. – Alex Davies Oct 29 '20 at 03:37
  • @AlexDavies you should probably ask another question. – wwii Oct 29 '20 at 16:13
  • @AlexDavies, to do it the way you describe, you would want a column that ennumerates which day in an ordinal fashion (eg. 1,2,3,4), and then pass that into the height arg of ax.bar(), which is where I passed in "df['Feed_Num']" – dancassin Oct 30 '20 at 16:12
  • @AlexDavies, you would also want to adjust the 2 in the plt.ylim(0,2) to be your max day value. – dancassin Oct 30 '20 at 16:13