Weather is something everyone deals with every day, so I wanted to look at data that feels familiar and see if the numbers match what we already notice. The Jena climate dataset comes from a weather station in Jena, Germany, run by the Max Planck Institute for Biogeochemistry. The station records a new reading every 10 minutes. Each reading includes the temperature (in degrees Celsius), the relative humidity (as a percent), and other things like air pressure and wind. The data runs from 2009 through 2016, so I had eight full years to work with. That made it an easy choice, because I could look at the same question in a lot of different ways and see if the answer stayed the same from year to year.
Most people notice that a cold, damp morning feels very different from a hot, dry afternoon. It seems like temperature and humidity move in opposite directions. This was the starting point for my project. I wanted to see how much temperature really affects humidity, and whether that holds up across the seasons, across the years, and across the hours of the day. I started with the big picture, which is how temperature changes over a year, and then looked closer step by step. I chose a data set with eight full years on purpose, so I could check whether a pattern was real or just a one-year thing. I also switched the temperature to Fahrenheit for my charts, even though the original data is in Celsius. This makes the ouput easier for me and others that might view my project to understand.
The data set has 420,551 rows and 15 columns. I focused on two of
them, temperature (T (degC)) and relative humidity
(rh (%)). The descriptive statistics show a wide range of
values for both. Temperature averaged about 9.5°C (about 49°F). The
standard deviation was 8.4 degrees, which means the readings are spread
out a lot around that average. The lowest reading was -23.0°C and the
highest was 37.3°C. This makes sense because Jena has four seasons, so a
day in winter and a day in summer can be very different. Humidity
averaged about 76%, with a median of 79%. Its standard deviation was
16.5 points, and readings ranged from 13% to 100%. The median is a
little higher than the average, which means a few very dry readings are
pulling the average down. Overall, there is a lot of variety in both
measurements, which makes them interesting to look at more closely.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.ticker import FuncFormatter
file_path = "jena_climate_2009_2016.csv"
df = pd.read_csv(file_path)
print(df.shape)
## (420551, 15)
print(df[['T (degC)', 'rh (%)']].describe())
## T (degC) rh (%)
## count 420551.000000 420551.000000
## mean 9.450147 76.008259
## std 8.423365 16.476175
## min -23.010000 12.950000
## 25% 3.360000 65.210000
## 50% 9.420000 79.300000
## 75% 15.470000 89.400000
## max 37.280000 100.000000
print(df[['T (degC)', 'rh (%)']].std())
## T (degC) 8.423365
## rh (%) 16.476175
## dtype: float64
# Get the data ready for the charts below
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
df["Date Time"] = pd.to_datetime(df["Date Time"], dayfirst=True)
df["Year"] = df["Date Time"].dt.year
df = df[df["Year"] <= 2016]
df["Month"] = df["Date Time"].dt.month
df["Hour"] = df["Date Time"].dt.hour
df["TempF"] = (df["T (degC)"] * 1.8) + 32
The first chart is a simple line chart showing the average temperature for each month across all eight years. I made it to get a feel for the seasons before building more detailed charts. It forms a smooth curve. January is the coldest month, and temperatures rise through the spring to a peak in July before coming back down. That is what you would expect for a city in central Germany, so it was good to see that the data matched real life. It also leads to the main question: if temperature changes this much over the year, what happens to humidity?
monthly_temp = df.groupby("Month")["T (degC)"].mean()
monthly_temp = (monthly_temp * 1.8) + 32
plt.figure(figsize=(12, 8))
plt.plot(monthly_temp.index, monthly_temp.values, marker="o")
plt.title("Average Temperature By Month In Jena Germany Across 2009-2016.")
plt.xlabel("Month")
plt.ylabel("Temperature In Degrees Fahrenheit.")
plt.xticks(range(1, 13), month_names)
plt.grid()
plt.show()
My second chart is a scatter plot that goes right at the main question. Each dot is one reading, with temperature along the bottom and humidity up the side. The dots slope downward from left to right. When it is cold, humidity is almost always high. When it gets warmer, the dots spread out and humidity is often much lower. The correlation is about -0.57, which I would call a moderate negative relationship. In simple terms, correlation is a number that shows how closely two things move together, and a negative number means that when one goes up, the other tends to go down. Temperature clearly matters, but the spread of the dots shows it does not explain everything. The downside of this chart is that there are so many dots on top of each other that it shows the direction of the trend but not how often each level of humidity happens. The next charts help with that.
temp_inF = (df["T (degC)"] * 1.8) + 32
plt.figure(figsize=(12, 8))
plt.scatter(temp_inF, df["rh (%)"], s=8, alpha=0.2)
plt.title("To What Extent Do Temperature And Relative Humidity Correlate")
plt.xlabel("Temperature (Fahrenheit)")
plt.ylabel("Relative Humidity (%)")
plt.grid()
plt.show()
This chart has one line for each year and shows the average humidity for each month. I made it to see if the pattern in the scatter plot happens every year or only shows up when all the data is combined. What stood out to me is how closely the eight lines follow the same shape. That tells me the pattern is not a one-year thing. Humidity is highest in the late fall and winter months and lowest in the spring and summer months. This is almost the opposite of the temperature chart, since the coldest time of year is also the most humid. That makes sense because cold air can hold less water vapor, so even a small amount of moisture makes the humidity number high.
humidity_df = df.groupby(['Month', 'Year'])['rh (%)'].mean().reset_index(name='AvgHumidity')
fig = plt.figure(figsize=(18,10))
ax = fig.add_subplot(1,1,1)
my_colors = {2009:'blue',
2010:'red',
2011:'green',
2012:'gray',
2013:'purple',
2014:'gold',
2015:'brown',
2016:'black'}
for key, grp in humidity_df.groupby('Year'):
grp.plot(ax=ax, kind='line', x='Month', y='AvgHumidity', color=my_colors[key], label=key, marker='8')
plt.title('Average Relative Humidity by Month and Year', fontsize=18)
ax.set_xlabel('Month', fontsize=18)
ax.set_ylabel('Average Relative Humidity (%)', fontsize=18, labelpad=20)
ax.tick_params(axis='x', labelsize=14, rotation=0)
ax.tick_params(axis='y', labelsize=14, rotation=0)
ax.set_xticks(np.arange(1, 13))
ax.set_xticklabels(month_names)
handles, labels = ax.get_legend_handles_labels()
plt.legend(handles, labels, loc='best', fontsize=14, ncol=1)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: ('%1.0f%%') % x))
plt.show()
This heatmap adds the time of day. Each square is the average temperature for one month at one hour of the day, with red for warmer and blue for cooler. I wanted to see how much the temperature changes during a single day, and whether that change is the same all year. In every month, the coolest part of the day is the early morning and the warmest is the afternoon. That makes sense because it takes time for the sun to heat things up, and the air keeps cooling overnight. The warmest squares are in the summer afternoons, and the coldest are in the winter nights and early mornings. The difference between the coolest and warmest hour of the day is bigger in the summer months than in the winter months. This shows that the time of day makes a big difference too, not just the season.
x3 = df.groupby(['Month', 'Hour'])['TempF'].mean().reset_index(name='AvgTempF')
hm_df = pd.pivot_table(x3, index='Month', columns='Hour', values='AvgTempF')
hm_df.index = month_names
fig = plt.figure(figsize=(18,10))
ax = fig.add_subplot(1,1,1)
comma_fmt = FuncFormatter(lambda x, p: format(int(x), ','))
ax = sns.heatmap(hm_df, linewidth = 0.2,
annot = True, cmap = 'coolwarm', fmt=',.0f',
square = True, annot_kws={'size':11},
cbar_kws = {'format' :comma_fmt, 'orientation' : 'vertical'})
plt.title('Heatmap of Average Temperature by Month and Hour of Day', fontsize=18, pad=15)
plt.xlabel('Hour (24 Hour Interval)', fontsize=18, labelpad=10)
plt.ylabel('Month', fontsize=18, labelpad=10)
plt.yticks(rotation=0, size=14)
cbar = ax.collections[0].colorbar
min_temp = int(hm_df.to_numpy().min())
max_temp = int(hm_df.to_numpy().max())
my_colorbar_ticks = [*range(min_temp - (min_temp % 10) + 10, max_temp, 10)]
cbar.set_ticks(my_colorbar_ticks)
my_colorbar_tick_labels = ['{:,}'.format(each) for each in my_colorbar_ticks]
cbar.set_ticklabels(my_colorbar_tick_labels)
cbar.set_label('Average Temperature (°F)', rotation = 270, fontsize=14, color='black', labelpad=20)
plt.show()
For the last chart, I sorted every humidity reading into three groups: low (under 60%), moderate (60 to 80%), and high (over 80%). Each bar shows what share of that month’s readings fall into each group, so every bar adds up to 100%. I made it to answer something the scatter plot could not, which is how often it is humid at different times of the year. In the winter months, a large share of the readings are in the high group, while in the summer months a much smaller share are. The low group goes the other way, growing from a small slice of the readings in winter to a much bigger slice in summer. This backs up the idea that the connection between temperature and humidity is real and not just something in the averages.
df['HumidityLevel'] = pd.cut(df['rh (%)'],
bins=[0, 60, 80, 100],
labels=['Low', 'Moderate', 'High'],
include_lowest=True)
stacked_df = df.groupby(['Month', 'HumidityLevel'], observed=False)['rh (%)'].count().reset_index(name='Readings')
stacked_df = stacked_df.pivot(index='Month', columns='HumidityLevel', values='Readings')
stacked_df = stacked_df.div(stacked_df.sum(axis=1), axis=0) * 100
level_order = ['Low', 'Moderate', 'High']
stacked_df = stacked_df.reindex(columns=reversed(level_order))
stacked_df.index = month_names
fig = plt.figure(figsize=(18,10))
ax = fig.add_subplot(1,1,1)
stacked_df.plot(kind='bar', stacked=True, ax=ax, color=['steelblue', 'mediumseagreen', 'gold'])
plt.ylabel('Share of Readings', fontsize=18, labelpad=10)
plt.title('Share of Humidity Readings by Month and Humidity Level \n Stacked Bar Plot', fontsize=18)
plt.xticks(rotation=0, horizontalalignment='center', fontsize=14)
plt.yticks(fontsize=14)
ax.set_xlabel('Month', fontsize=18)
handles, labels = ax.get_legend_handles_labels()
handles = [ handles[2], handles[1], handles[0] ]
labels = [ labels[2], labels[1], labels[0] ]
plt.legend(handles, labels, loc='best', fontsize=14)
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: ('%1.0f%%') % x))
plt.show()
Before looking closely at the Jena data, I wanted to find out whether temperature really affects humidity or whether other things matter more. Many things could change how humid the air is, like rain, wind, air pressure, and the time of day, so I wanted to see how far temperature alone could explain it. After looking at eight years of readings, I found a clear relationship, and it went in the opposite direction. Warmer times had lower humidity, and colder times had higher humidity. The scatter plot had a correlation of about -0.57, which is a moderate negative relationship. The yearly line chart backed that up, since the lines followed nearly the same shape each year, with humidity highest in the cold months and lowest in the warm ones. The heatmap showed that temperature also changes a lot during a single day. The last chart made it easy to picture, since most winter readings were highly humid while far fewer summer readings were. At the same time, many dots in the scatter plot do not follow the trend, so temperature is not the only thing at work. One more thing to keep in mind is that relative humidity depends on temperature by the way it is measured, since it compares the moisture in the air to how much the air can hold at that temperature. So part of this relationship comes from the measurement itself. Rain, wind, and air pressure are also in the dataset and likely matter too, so I can’t say temperature is the only reason humidity changes. What I can say is that in Jena from 2009 to 2016, temperature had a clear and steady relationship with humidity, across the seasons, the years, and the hours of the day.