matplotlib plot circular daily-cycle diagram (daily polar plot) - python

I am trying to plot the daily cycle of a sampling strategy as a kind of rose diagram / polar plot.
I want to show each of our experiment samples where the distance from the center is the day, and the angle from 0 represents the time at which that sample was collected. Ideally, I would like to be able to colour the points by different variables.
The ideal plot should look something like below:
Simulate dummy data to explain the problem
I have the data in an xarray format. We have a launchtime dimension that contains the time at which a sample was taken, and we want to use this to plot when in the day, and then each of the days in turn.
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from pandas.tseries.offsets import DateOffset
import matplotlib.dates as mdates
import itertools
value = np.random.normal(size=100)
expected_time = pd.date_range("2000-01-01", freq="180min", periods=100)
# add random offset to simulate being +/- the true expected release time
time_deltas = np.array([DateOffset(minute=max(0, min(int(i), 59))) for i in np.abs(np.random.normal(0, 10, size=100))])
time = [expected_time[i] + time_deltas[i] if (i % 2 == 0) else expected_time[i] - time_deltas[i] for i in range(100)]
df = pd.DataFrame({"launchtime": time, "value": value})
ds = df.set_index("launchtime").to_xarray()
ds = ds.assign_coords(expected_time=("launchtime", expected_time))
My thinking so far
def time_to_angle(dt: pd.Timestamp) -> float:
SEC_IN_DAY = 86_400
start_of_day = pd.to_datetime(f"{dt.day}-{dt.month}-{dt.year}")
delta = (dt - start_of_day)
n_seconds = delta.seconds
# return angle in degrees
return (n_seconds / SEC_IN_DAY) * 360
# angle from 0 degrees
angles = [time_to_angle(pd.to_datetime(dt)) for dt in ds.launchtime.values]
# how far along the radius
days = np.arange(np.unique(ds["launchtime.day"].values).size)
# how to plot in polar coordinates? Do I have to draw an x,y grid and plot as a scatter?
Any advice on how to go about addressing this problem would be super appreciated!

with a bit of high/secondary school trigonometry it can be quite simply achieved as a scatter plot.
consider the time of day as the angle calculated in radians
consider the day (how old) as the radius
it then reduces to simple use of sin() and cos() to calculated x and y co-ordinates
for good measure decided to colour points too... not that happy with a clock face showing 24hrs, analogue clocks only show 12 hours
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import math
fig, ax = plt.subplots(1,1, figsize=(4, 4))
df = pd.DataFrame({"sampledate":pd.date_range("01-apr-2021", "09-apr-2021 23:59", freq="30s")})
# drop a bit of data so it's not perfect circles...
df = df.loc[np.random.choice(df.index, int(len(df)/200) )]
df["angle"] = ((df["sampledate"] - df["sampledate"].dt.floor("D")).dt.total_seconds() / (24*60*60)) * 2*math.pi
df["radius"] = (df["sampledate"] - df["sampledate"].min()).dt.days+1
# scatter, radius is how old sample is, angle is time of day
ax.scatter(x=df["angle"].apply(math.sin) * df["radius"], y=df["angle"].apply(math.cos) * df["radius"],
c=np.where(df.sampledate.dt.hour.le(12), "red", "pink"), s=10)
ax.axis("off")
# draw markers on clock face...
for h in list(range(0, 24, 3)):
a = (h/24)*2*math.pi
x = math.sin(a)*df["radius"].max()
y = math.cos(a)*df["radius"].max()
ax.annotate(h, xy=(x, y), xytext=(x*1.1,y*1.1), backgroundcolor="yellow")

We can use the projection="polar" argument to the ax = plt.subplot(projection='polar') argument in order to create a plot with:
the angle showing the time of day (theta, defined in radians)
the radius showing number of days since the first sample (r).
We need to do some initial pre-processing of the time of day data and the
from sklearn.preprocessing import LabelEncoder
def time_to_angle(dt: pd.Timestamp) -> float:
SEC_IN_DAY = 86_400
start_of_day = pd.to_datetime(f"{dt.day}-{dt.month}-{dt.year}")
delta = (dt - start_of_day)
n_seconds = delta.seconds
# return angle in radians
return (n_seconds / SEC_IN_DAY) * 2 * np.pi
# angle from 0 degrees
angles = [time_to_angle(pd.to_datetime(dt)) for dt in ds.launchtime.values]
# how far along the radius = DAY NUM
le = LabelEncoder()
days = le.fit_transform(ds["launchtime.dayofyear"].values)
# Use the polar projection plot from matplotlib
ax = plt.subplot(projection='polar')
ax.set_theta_direction(-1)
ax.set_theta_zero_location("N")
ax.scatter(angles, days, marker="o")
ax.set_xticklabels(['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00',])
ax.set_yticklabels([])
ax.set_title("Sampling Schedule [UTC]")
plt.show()
The output looks something like this:

Related

python - frequency of power spectrum

I want to plot a power spectrum from my data set (array of about 2000 values, the data is recorded every minute).
I've gotten so far as:
y= np.fft.fft(data)
abs = np.abs(y) #absolute value
p = np.square(abs) #power
but am confused about setting the frequency.
I've tried using freqs = np.fft.fftfreq(len(y)), but when I plot the result it looks like, which can't be right.
What am I doing wrong?
Here is an example to plot the power spectrum:
import matplotlib.pyplot as plt
import numpy as np
t = np.linspace(0,2000,200)
data = 2 * np.sin(2*np.pi *60*t) + 2 * np.sin(2*np.pi *42*t)
spectrum = np.fft.fft(data)
power_spectrum = np.square(np.abs(spectrum))
fig, ax = plt.subplots()
ax.plot(np.arange(len(power_spectrum)), power_spectrum)
plt.show()

Animating and exporting the cartopy Nightshade feature

I am trying to plot the groundtrack of a satellite through a combination of packages, animate the satellite movement, mark a field of view from the subsatellite point (which is just arbitrary circles in this code) and then export the file as a video of some kind. So far, I have been able to do all of this except that when I try to export the video, the Nightshade feature doesn't animate so much as overlay and eventually blacks out most of the screen. Is there something I'm missing on how to properly animate the Nightshade feature? I know that I'm essentially recreating a new feature inside the update function everytime it runs a frame but I could not figure out how to update it as I do the scatter plots.
I've included my sample code below.
import pandas as pd
from sgp4.api import WGS72
from sgp4.api import Satrec
from skyfield.api import EarthSatellite, load, N, W, wgs84
import datetime
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.animation as animation
from cartopy.feature.nightshade import Nightshade
# CREATE THE SATELLITE DATA
epoch = datetime.date(1949, 12, 31)
sat = Satrec()
sat.sgp4init(
WGS72, # gravity model
'i', # 'a' = old AFSPC mode, 'i' = improved mode
5, # satnum: Satellite number
(datetime.date.today() - epoch).days, # epoch: days since 1949 December 31 00:00 UT
0, # bstar: drag coefficient (1/earth radii)
6.969196665e-13, # ndot (NOT USED): ballistic coefficient (revs/day)
0.0, # nddot (NOT USED): mean motion 2nd derivative (revs/day^3)
0.1, # ecco: eccentricity
280 * np.pi / 180, # argpo: argument of perigee (radians)
50 * np.pi / 180, # inclo: inclination (radians)
275 * np.pi / 180, # mo: mean anomaly (radians)
0.0472294454407, # no_kozai: mean motion (radians/minute)
50 * np.pi / 180, # nodeo: right ascension of ascending node (radians)
)
# DEFINE A FEW BASIC PARAMETERS FOR THE PROGRAM
P = sat.mo / sat.no_kozai # min, period of orbit. LEOs orbit between 84-127 minutes
ts = load.timescale()
sat1 = EarthSatellite.from_satrec(sat, ts)
hours = np.arange(0, 6, 0.05)
time = ts.utc(2021, 6, 31, hours)
pos = sat1.at(time).position.km
pos_ec = sat1.at(time).ecliptic_position().km
sp = wgs84.subpoint(sat1.at(time))
latitude = sp.latitude
longitude = sp.longitude
elev = sp.elevation
# CREATE A DATAFRAME OF THE DATA FOR REVIEW LATER IF NEEDED
df = pd.DataFrame([time.utc_datetime(), latitude.degrees, longitude.degrees, elev.km],
index=['DTS', 'lat', 'lon', 'elev']).T
df.lat = df.lat.astype('float32')
df.lon = df.lon.astype('float32')
df.elev = df.elev.astype('float32')
df.set_index('DTS', inplace=True)
# ASSIGN RELEVANT DATA FOR THE SUBSATELLITE POINT
ssp = np.transpose(np.array([longitude.degrees, latitude.degrees]))
line = ssp.copy()
pos = np.where(np.diff(np.abs(line[:, 0] >= 0)))[0]
line[pos, :] = np.nan
# CREATE DATE TIME RANGES FOR USE WITH THE NIGHTSHADE FEATURE
base = datetime.datetime(2000, 1, 1)
dates = np.array([base + datetime.timedelta(hours=i) for i in range(len(hours))])
shades = [Nightshade(date, alpha=0.2) for date in dates]
### CREATE FIGURE AND IMAGE
fig = plt.figure(figsize=(16, 8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.stock_img()
# plot lines that will show the ground track that will be animated
ax.plot(line[:, 0], line[:, 1], '--k')
# create a blank scatter to start
scatter = ax.scatter(None, None, color='r', s=30)
# initiate the circles around the scatter point
circle1 = plt.Circle((longitude.degrees[0], latitude.degrees[0]), radius=30, color='blue', alpha=0.3)
circle2 = plt.Circle((longitude.degrees[0], latitude.degrees[0]), radius=40, color='yellow', alpha=0.3)
# add the circles to the axis
ax.add_patch(circle1)
ax.add_patch(circle2)
# Add the nightshade feature (but set it to be invisible so it doesn't stay through the whole animation)
ns = ax.add_feature(Nightshade(base, alpha=0.0))
# Create all the updates for the animation
def update(i):
lon = ssp[i, 0]
lat = ssp[i, 1]
scatter.set_offsets(np.c_[lon, lat])
# add a feature for the next Nightshade feature
ns = ax.add_feature(shades[i], alpha=0.2)
circle1.center = (lon, lat)
circle2.center = (lon, lat)
return scatter, circle1, circle2, ns
# Run the animation
anim = animation.FuncAnimation(plt.gcf(), update, frames=df.shape[0],init_func=None, interval=250, blit=True)
plt.show()
# WRITE THE VIDEO
Writer = animation.writers['ffmpeg']
writer = Writer(fps=10, metadata=dict(artist='Me'), bitrate=1800)
anim.save('gt.mp4', writer=writer)

What is the best way to plot SODAR vertical wind profiles over time in python?

I am able to do the typical wind barb and vector plots. I am looking to do a color contour kind of map and can't figure out how to do it. Does anyone have any ideas? Is there a package for this in MetPy? There are some examples in the article in the website below. I currently have my data in panda dataframes but I can ofcourse convert that to whatever is necessary.
https://asr.copernicus.org/articles/17/109/2020/
You can accomplish this using the standard contour plotting method from matplotlib. Here's a contour plot using some created synthetic data:
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import numpy as np
z = np.linspace(0, 10000, 100)
t = [datetime.utcnow() + timedelta(minutes=15) * i for i in range(40)]
data = z[:, None] * np.arange(40)
plt.contour(t, z, data)
plt.xticks(rotation=45)
This yields:
For the plots in the publication, that looks like something you'd want to use pcolormesh to accomplish:
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import numpy as np
z = np.linspace(0, 10000, 100)
t = [datetime.utcnow() + timedelta(minutes=15) * i for i in range(40)]
data = np.random.randn(len(z), len(t))
plt.pcolormesh(t, z, data, shading='auto')
plt.xticks(rotation=45)
This results in this image:

Plotting times versus dates while skipping unwanted dates in Python

I want to make a program that monitors my 5000 meters progress. Inspired by this and this, I tried to make it work by combining some of the answers without any luck.
from __future__ import division
from matplotlib import pyplot as plt
from matplotlib.ticker import FuncFormatter
import matplotlib.dates as mdates
import numpy as np
import datetime as dt
def equidate_ax(fig, ax, dates, fmt="%d.%m.%Y", label="Date"):
N = len(dates)
def format_date(index, pos):
index = np.clip(int(index + 0.5), 0, N - 1)
return dates[index].strftime(fmt)
ax.xaxis.set_major_formatter(FuncFormatter(format_date))
ax.set_xlabel(label)
fig.autofmt_xdate()
def DistVel2Time(distance, velocity_kph):
velocity_ms = velocity_kph / 3.6
time_sec = distance / velocity_ms
hours = int(time_sec//3600)
minutes = int((time_sec%3600)//60)
seconds = int(time_sec%60)
return "{:02d}:{:02d}".format(minutes, seconds)
times = [DistVel2Time(a, b) for a, b in [(5000, 13), (5000, 15), (5000, 14)]]
dates = [dt.datetime(year, month, day) for year, month, day in [(2019,2,1), (2019,2,2), (2019,2,7)]]
fig_1, ax_1 = plt.subplots()
ax_1.plot(dates, times, 'o--')
ax_1.xaxis_date()
ax_1.xaxis.set_major_formatter(mdates.DateFormatter('%d.%m.%Y'))
#ax_1.yaxis_date()
#ax_1.yaxis.set_major_formatter(mdates.DateFormatter("%M:%S"))
fig_1.autofmt_xdate()
plt.show()
fig_2, ax_2 = plt.subplots()
ax_2.plot(dates, times, 'D--')
ax_2.xaxis_date()
ax_2.xaxis.set_major_formatter(mdates.DateFormatter('%d.%m.%Y'))
equidate_ax(fig_2, ax_2, dates)
plt.show()
fig_1.savefig('fig1.png')
fig_2.savefig('fig2.png')
I stole the equidate_ax from #ascripter (from the second link) because I would like to skip all dates that I do not run.
If I run this piece of code, and save the figures, I end up getting the following two figures that are rather strange, as the y-axis does not distinguish between lower or higher values (Figures 1 and 2), and the x-axis for Figure 2 is repeating itself.
Figure 1: fig_1 from the code above.
Figure 2: fig_2 from the code above.
Why is not the y-axis plotting correctly in terms of lower or higher values?
How can I prevent the equidate_ax function from repeating itself and rather skip the unwanted dates?
If anyone could help cleaning up my mess, I would be grateful.
Combining the answers from the questions linked:
You basically have to make sure that matplotlib cannot guess the format of the x-axis but can guess the format of the y-axis.
With this matplotlib will not try to be smart and add dates you do not want to display on the x-axis but at the same time will be smart and sort the times for you on the y-axis.
from __future__ import division
from matplotlib import pyplot as plt
from matplotlib.ticker import FuncFormatter
import matplotlib.dates as mdates
import numpy as np
import datetime as dt
def DistVel2Time(distance, velocity_kph):
velocity_ms = velocity_kph / 3.6
time_sec = distance / velocity_ms
hours = int(time_sec//3600)
minutes = int((time_sec%3600)//60)
seconds = int(time_sec%60)
# note that I return a timedelta object here
return dt.timedelta(minutes=minutes, seconds=seconds)
# we have to choose a interpretable data-type here, simply take the total time needed in seconds
times = [ DistVel2Time(a, b).total_seconds() for a, b in [(5000, 13), (5000, 15), (5000, 14)]]
# here we want to make sure that matplotlib cannot interpret it so we use strings directly
# change the format as required
dates = [ "%00d.%00d.%000d" % ymd for ymd in [(2019,2,1), (2019,2,2), (2019,2,7)]]
# the formatting function taken from https://stackoverflow.com/questions/48294332/plot-datetime-timedelta-using-matplotlib-and-python
def format_func(x, pos):
hours = int(x//3600)
minutes = int((x%3600)//60)
seconds = int(x%60)
return "{:d}:{:02d}:{:02d}".format(hours, minutes, seconds)
formatter = FuncFormatter(format_func)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(dates, times, 'o--')
ax.yaxis.set_major_formatter(formatter)
plt.show()
It will produce a plot like this:
Although #milck answered my questions, I made a more streamlined version myself inspired by his answer and the previously mentioned answers from the question.
from matplotlib import pyplot as plt
from matplotlib.ticker import FuncFormatter
def DistVel2Time(*velocity_kph):
distance = 5000
times = [int(distance / (_ / 3.6)) for _ in velocity_kph]
return times
times = DistVel2Time(13, 15, 14)
dates = ["%00d.%00d.%000d" % dmy for dmy in [(1,2,2019), (2,2,2019), (7,2,2019)]]
def format_func(x, pos):
#hours = int(x//3600)
minutes = int((x%3600)//60)
seconds = int(x%60)
return "{:02d}:{:02d}".format(minutes, seconds)
formatter = FuncFormatter(format_func)
fig, ax = plt.subplots()
ax.plot(dates, times, 'D--')
ax.yaxis.set_major_formatter(formatter)
fig.autofmt_xdate()
plt.show()
This is shorter and perhaps easier to understand.

Visualizing time series in spirals using R or Python?

Does anyone know how to do this in R? That is, represent this cyclical data from the left plot to the right plot?
http://cs.lnu.se/isovis/courses/spring07/dac751/papers/TimeSpiralsInfoVis2001.pdf
Here is some example data.
Day = c(rep(1,5),rep(2,5),rep(3,5))
Hour = rep(1:5,3)
Sunlight = c(0,1,2,3,0,1,2,3,2,1,0,0,4,2,1)
data = cbind(Day,Hour,Sunlight)
This seems pretty close:
# sample data - hourly for 10 days; daylight from roughly 6:00am to 6:00pm
set.seed(1) # for reproducibility
Day <- c(rep(1:10,each=24))
Hour <- rep(1:24)
data <- data.frame(Day,Hour)
data$Sunlight <- with(data,-10*cos(2*pi*(Hour-1+abs(rnorm(240)))/24))
data$Sunlight[data$Sunlight<0] <- 0
library(ggplot2)
ggplot(data,aes(x=Hour,y=10+24*Day+Hour-1))+
geom_tile(aes(color=Sunlight),size=2)+
scale_color_gradient(low="black",high="yellow")+
ylim(0,250)+ labs(y="",x="")+
coord_polar(theta="x")+
theme(panel.background=element_rect(fill="black"),panel.grid=element_blank(),
axis.text.y=element_blank(), axis.text.x=element_text(color="white"),
axis.ticks.y=element_blank())
I know how to do this in Python. I find the scatter plot from matplotlib good for this sort of thing. Here's an example:
import matplotlib.pyplot as plt
import numpy as np
period = 0.5
f = np.arange(0, 100, 0.03) // Data range
z = np.sin(f) // Data
a = f*np.sin(period*f);
b = f*np.cos(period*f);
fig = plt.figure()
ax = plt.subplot(111)
fig.add_subplot(ax)
ax.scatter(a, b, c=z, s=100, edgecolors='none')
plt.show()
You can change period to change the number of revolutions in the spiral. a and b plot the spiral whilst z contains the actual data (in this example, a sine wave).

Categories

Resources