Calculating Apparent (Feels Like) Temperature

With Dark Sky going away and the integrated Dark Sky add-on bundled into a single “Weather” entity, I decided to do some calculations for a value that I use a lot (and is missing from most weather data): the feels like temperature.

This is all based on the values Fahrenheit, MPH, and Relative Humidity by percentage. So, if you want this in Celsius then you’ll have to modify it a bit.

There are some additional calculations that can be done if you have access to the heat index, but this is just a basic one for now and I may update it later for heat index. The formulas are based on the Steadman scale, which is not 100% accurate but accurate enough for most people wanting this type of information.

The only requirements are that you have access to temperature, humidity and wind speed to determine the feels like temperature. I did this in a sensor template (replace my Dark Sky attributes with your own weather data values):

template:
  - sensor:
	  - name: "Current Temperature Feels Like"
		unique_id: "3ead8c38-f7b4-49b7-9054-926063ed4d41"
		unit_of_measurement: "°F"
		device_class: temperature
		icon: mdi:thermometer-lines
		state: >-
		  {% set temperature = state_attr('weather.dark_sky','temperature') %}
		  {% set windspeed = state_attr('weather.dark_sky','wind_speed') %}
		  {% set humidity = state_attr('weather.dark_sky','humidity') %}
		  {% set feelslike = temperature %}

		  {% if temperature <= 50 and humidity >= 3 %}
			{% set feelslike = 35.74 + (0.6215 * temperature) - 35.75 * (windspeed ** 0.16) + ( (0.4275 * temperature) * (windspeed ** 0.16) ) %}
		  {% endif %}

		  {% if feelslike == temperature and temperature >= 80 %}
			{% set feelslike = 0.5 * (temperature + 61 + ((temperature - 68) * 1.2) + (humidity * 0.094) ) %}

			{% if feelslike >= 80 %}
			  {% set feelslike = - 42.379 + 2.04901523 * temperature + 10.14333127 * humidity - 0.22475541 * temperature * humidity - 0.00683783 * temperature * temperature - 0.05481717 * humidity * humidity + 0.00122874 * temperature * temperature * humidity + 0.00085282 * temperature * humidity * humidity - 0.00000199 * temperature * temperature * humidity * humidity %}    

			  {% if humidity < 13 and temperature >= 80 and temperature <= 112 %}
				{% set feelslike = feelslike - ((13 - humidity) / 4) * math.sqrt((17 - math.fabs(temperature - 95.0)) / 17) %}

				{% if humidity > 85 and temperature >= 80 and temperature <= 87 %}
				  {% set feelslike = feelslike + ((humidity - 85) / 10) * ((87 - temperature) / 5) %}
				{% endif %}
			  {% endif %}
			{% endif %}
		  {% endif %}

		  {{ float(feelslike, 0) | round(0) }}
1 Like

I actually have a heat index calculation that I do for some things.

if you are interested I can post it here.

Oh yea, that would be cool. I have it on my dark sky sensors but when that goes away at the end of next year I might need to calculate that myself (although the above calc is pretty accurate for temps over 80).

Here is the one for my master bedroom:

sensor:
  - platform: template
    sensors:
      master_bedroom_heat_index:
        friendly_name: "Master Bedroom Heat Index"
        unit_of_measurement: '°F'
        value_template: >
          {% set T = states.sensor.master_bedroom_temperature.state | float %}
          {% set RH = states.sensor.master_bedroom_humidity.state | float %}
          {% set HI_full = ((-42.379) + (2.04901523*T) + (10.14333127*RH) - (0.22475541*T*RH) - (0.00683783*T*T) - (0.05481717*RH*RH) + (0.00122874*T*T*RH) + (0.00085282*T*RH*RH) - (0.00000199*T*T*RH*RH)) | round (2) %}
          {% set adj1 =  (((13-RH)/4)*((17-(T-95.0)|abs)/17)|sqrt) | round (2) %}
          {% set adj2 = ((((RH-85)/10) * ((87-T)/5)))| round (2) %}
          {% set HI_simple = (0.5 * (T + 61.0 + ((T-68.0)*1.2) + (RH*0.094))) |round (2) %}
          {% if ((HI_simple + T)/2) >= 80 %}
            {% if RH < 13 and (T > 80 and T < 112) %}
              {{ (HI_full - adj1) | round (1) }}
            {% elif RH > 85 and (T > 80 and T < 87 ) %}
              {{ (HI_full + adj2) | round (1) }}
            {% else %}
              {{HI_full | round (1)}}
            {%endif%}
          {% else %}
            {{HI_simple | round (1)}}
          {% endif %}

Oh yea, I saw that post when I was hunting around for apparent temperatures, I didn’t even notice it was yours :slight_smile: . So that heat index would be valid for outdoors as well as indoors? The indoors and shade formulas differ slightly from outdoor shade-less calculations. I thought it was interesting when I read it because the article on the formula that you used was the exact one I found when I was searching online for a good formula. In fact the only reason I didn’t use what you posted was the outdoor thing and lack of wind in the calc (because, obviously, indoors!).

And isn’t your calc going to break soon with that " | float" statement since it’s depreciated for the newer “float(X, Y)” method?

no I don’t think so. If it was going to break it would have already started giving me warnings and so far I don’t have any templates with those warnings and almost none of my conversions ever use a default.

well, I did have a couple that were complaining (but never these) because the entities that they referenced weren’t initialized at startup. but as soon as the entities popped up the warnings went away. I went ahead and fixed them anyway but a couple of errors right at restart doesn’t bother me too badly.

As far as inside vs outside I don’t think it makes any difference for the heat index. it’s literally just based on temp & humidity.

I believe there is a difference between true “heat index” & “feels like” and it’s likely what you said where it takes into account wind speed and such. But I’m not really sure.

And if the outside wind speed makes a difference then the use of fans/window AC units/etc would affect the inside “feels like” as well. And those are harder to account for since they aren’t (typically) measured.

I really don’t even use the heat index I calculate anywhere. It was mostly an academic exercise and it was another interesting data point I could track.

I thought about doing the same for wind chill (outside only obviously) but never got around to it yet.

This should be wind chill (far easier than feels like) in F:

template:
  - sensor:
	  - name: "Current Temp With Wind Chill"
		unique_id: "a8aabfd9-52b8-4cae-a7f2-7f0607aa8335"
		unit_of_measurement: "°F"
		device_class: temperature
		icon: mdi:thermometer-lines
		state: >-
		  {% set temperature = state_attr('weather.dark_sky','temperature') %}
		  {% set windspeed = state_attr('weather.dark_sky','wind_speed') %}

		  {% set velocity = windspeed ** 0.16 %}
		  {% set windchill = 35.74 + (0.6215 * temperature)-(35.75 * velocity) +(0.4275 * temperature * velocity) %}

		  {{ float(windchill, 0) | round(0) }}
1 Like

By the way, the wind chill compared to the feels like equation above come out pretty much the same since the temperature is less than 80. I would have to run side-by-side down up the scale but I think it’ll probably deviate right around 60 slightly and get further apart the closer to 80 you get.

Thanks for sharing, but there must a calculation error.
If my current recorded temperature is 41.2, my humidity is 98, and wind speed is 0, the result is 61, which is obviously wrong. It looks like 0 wind breaks the calculation.

          {% set temperature = states('sensor.home_outside_temperature') | float %}
		  {% set windspeed = states('sensor.home_outside_wind_speed')  | float %}
		  {% set humidity = states('sensor.home_outside_humidity')  | float %}

		  {% set feelslike = temperature %}

		  {% if temperature <= 50 and humidity >= 3 %}
			{% set feelslike = 35.74 + (0.6215 * temperature) - 35.75 * (windspeed ** 0.16) + ( (0.4275 * temperature) * (windspeed ** 0.16) ) %}
		  {% endif %}

		  {% if feelslike == temperature and temperature >= 80 %}
			{% set feelslike = 0.5 * (temperature + 61 + ((temperature - 68) * 1.2) + (humidity * 0.094) ) %}

			{% if feelslike >= 80 %}
			  {% set feelslike = - 42.379 + 2.04901523 * temperature + 10.14333127 * humidity - 0.22475541 * temperature * humidity - 0.00683783 * temperature * temperature - 0.05481717 * humidity * humidity + 0.00122874 * temperature * temperature * humidity + 0.00085282 * temperature * humidity * humidity - 0.00000199 * temperature * temperature * humidity * humidity %}    

			  {% if humidity < 13 and temperature >= 80 and temperature <= 112 %}
				{% set feelslike = feelslike - ((13 - humidity) / 4) * math.sqrt((17 - math.fabs(temperature - 95.0)) / 17) %}

				{% if humidity > 85 and temperature >= 80 and temperature <= 87 %}
				  {% set feelslike = feelslike + ((humidity - 85) / 10) * ((87 - temperature) / 5) %}
				{% endif %}
			  {% endif %}
			{% endif %}
		  {% endif %}

		  {{ float(feelslike, 0) | round(0) }}    

Zero wind would break it because it relies on wind speed to determine feels like, that can easily be resolved with a condition for if wind speed = 0. You could put in there that if wind speed is zero then use a value of 1 and that should be fairly accurate.

Feels like calculations are not perfect, even the best algorithm has it getting inaccurate at high temperatures, that’s where heat index comes into play instead.

I think most services that provide wind speed use the same Steadman scale, but might have conditions for certain points that break the algorithm. For me, at least, my feels like and the “commercial” feels like is always within 0.5 degrees different if the temp/wind/humidity are close.

I could do with a sanity check. I’ve used the Steadman scale to calculate Apparent Temperature, using a weather station on my road. It works as expected when considering temperature, humidity and wind speed. However the weather station also measures solar radiation, which the Steadman scale can also include, to provide an “in the sun” Apparent Temperature.

If I plug in the current levels, I’m seeing an AT “in the shade” of 30.9 (celcius; 87.6 fahrenheit), which is slightly higher than the air temperature and seems fine. However the “in the sun” AT is 59 degrees (138 fahrenheit). If I substitute a solar radiation figure of 1,000 W/m2, which its been at plenty of times over the summer, then the AT comes in at 80 degrees (176 fahrenheit). Now on the one hand I’m aware that temperatures in the sun are going to be way higher, but that surely isn’t right - honestly I can’t conceive of the air feeling like its that hot…

If you look at the formula, its adding 0.044 x solar radiation, which means adding 44 degrees on a sunny day. Am I missing something here or is that right?

Is that per day? Per hour?

Basically, you need to know the units of your value. I’ve never seen any site list the value as W/m2. It’s always Wh/m2 over the span of the day or year (which ultimately ends up being kWh/m2). I.e. Check your units, divide the result accordingly based on your temperature update frequency.

Current solar radiation is typically measured in watts per square metre, and that’s the unit the Steadman formula uses. For sure deriving a per hour or per day energy figure could also be useful, such as if you’re looking to calculate likely production from solar panels. That’s no different to measuring both the current power an appliance is using (in watts), alongside the energy over time (in kWh) - they’re two different things.

My point was, I’ve never seen anyone report W/m2. They all report daily or yearly values. Are you sure the device that you’re getting information from is momentary?

If the value is in the 1000’s, it’s most likely daily.

Here’s a graph showing the solar radiation from my neighbors weather station, with the sun’s elevation also shown:

So you can see its definitely not cumulative, and maxes out around 880 W/m2. Just to show this is typical data, here’s a random weather station I found:

If you pick “Daily Detail” you’ll see they measure both Solar kWh and Max Solar, measured in W/m2. The former has been at around 6kWh for the last week, and the former around 880 W/m2.

All that is to say that a figure in the 800 region is fine for current solar radiation expressed in W/m2.

I realize I’m a bit late to the thread, but I’m considering a template for “feels like” based upon both heat index and wind chill. Hopefully, this will address the inaccurate or stale data being reported by the NWS integration (perhaps the API?). Right now, I’m using a spreadsheet to verify all the special cases. Ultimately, I’d like to figure out an algorithm that gracefully handles the range between 50°F and 80°F, even if it’s just a gross, but reasonable estimate.

FYI, when I go to Wind Chill Calculator and put in 0 mph for the wind speed, this pops up:

image

So, for wind speed less than 3 mph (are you using RH?), it appears we should use the ambient temperature. Similarly, for temperatures above 50°F, this pops up:

image

And when I go to Heat Index Calculator, this pops up:

image

Thankfully, there’s a simplified heat index equation for temperatures below 80°F at https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml. The referencing text is a bit ambiguous regarding how it’s actually applied. If you look at the equation, it appears the temperature is already averaged with ‘the simple formula.’ I’m not sure, but it appears you might be testing for temperature >= 80 instead of <80.

image

This template uses the NOAA’s National Weather Service and Environment Canada’s Meteorological Service of Canada wind chill and heat index equations to determine feels-like temperature. It’s unique in that it also seamlessly fills in the range between 50°F and 70°F using a simple weighted average.

Appreciation to @masterkenobi for his elegant version, which I used as a starting point for modification. Substitute your own sensors for temperature, relative humidity and wind speed.

  - platform: template
    sensors:
      # Feels-like temperature sensor template inspired by @masterkenobi:
      # References:
      # http://solberg.snr.missouri.edu/gcc/OFCMWindchillReport.pdf)
      # https://www.weather.gov/media/epz/wxcalc/windChill.pdf
      # https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
      # https://journals.ametsoc.org/downloadpdf/journals/apme/23/12/1520-0450_1984_023_1674_ausoat_2_0_co_2.xml
      kxxx_feels_like_temperature:
        friendly_name: "Kxxx Feels-Like Temperature"
        value_template: >
          {% set T = states( 'sensor.kxxx_temperature' ) | float %}
          {% set RH = states( 'sensor.kxxx_relative_humidity' ) | float %}
          {% set WS = states( 'sensor.kxxx_wind_speed' ) | float %}
          {% set WC = T | float %}

          {% if T <= 70 and WS >= 3 %}
            {% set WC = 35.74 + (0.6215*T) - 35.75*(WS**0.16) + ((0.4275*T)*(WS**0.16)) %}
          {% endif %}

          {% set STEADMAN_HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (RH*0.094)) %}
          {% if STEADMAN_HI >= 80 %}
            {% set ROTHFUSZ_HI = -42.379 + 2.04901523*T + 10.14333127*RH - 0.22475541*T*RH - 0.00683783*T*T - 0.05481717*RH*RH + 0.00122874*T*T*RH + 0.00085282*T*RH*RH - 0.00000199*T*T*RH*RH %}
            {% set HI = ROTHFUSZ_HI %}
            {% if RH < 13 and 80 < T < 112 %}
              {% set ADJUSTMENT = ((13-RH)/4)*((17-(T-95)|abs)/17)**0.5 %}
              {% set HI = HI - ADJUSTMENT %}
            {% elif RH > 85 and 80 < T < 87 %}
              {% set ADJUSTMENT = ((RH-85)/10) * ((87-T)/5) %}
              {% set HI = HI + ADJUSTMENT %}
            {% endif %}
          {% else %}
            {% set HI = STEADMAN_HI %}
          {% endif %}

          {% if T < 50 %}
            {% set FL = WC %}
          {% elif 50 <= T < 70 %}
            {% set FL = (((70-T)/20)*WC) + (((T-50)/20)*HI) %}
          {% elif T >= 70 %}
            {% set FL = HI %}
          {% endif %}
          {{- FL | round(1) -}}
        unit_of_measurement: "°F"
3 Likes