SkewT and Hodograph

Unidata Logo

Upper Air and the Skew-T Log-P

Unidata Python Workshop


Example Skew-T

Questions

  1. Where can upper air data be found and what format is it in?
  2. How can I obtain upper air data programatically?
  3. How can MetPy be used to make a Skew-T Log-P diagram and associated fiducial lines?
  4. How are themodynamic calculations performed on upper-air data?

Table of Contents

  1. Obtain upper air data
  2. Make a Skew-T
  3. Thermodynamics
  4. Plotting a Hodograph
  5. Advanced Layout

1. Obtain upper air data

Overview

Upper air observations are generally reported as a plain text file in a tabular format that represents the down sampled raw data transmitted by the rawinsonde. Data are reported an mandatory levels and at levels of significant change. An example of sounding data may look like this:

-----------------------------------------------------------------------------
   PRES   HGHT   TEMP   DWPT   RELH   MIXR   DRCT   SKNT   THTA   THTE   THTV
    hPa     m      C      C      %    g/kg    deg   knot     K      K      K 
-----------------------------------------------------------------------------
 1000.0    270                                                               
  991.0    345   -0.3   -2.8     83   3.15      0      0  273.6  282.3  274.1
  984.0    403   10.2   -7.8     27   2.17    327      4  284.7  291.1  285.0
  963.0    581   11.8   -9.2     22   1.99    226     17  288.0  294.1  288.4
  959.7    610   11.6   -9.4     22   1.96    210     19  288.1  294.1  288.5

Data are available to download from the University of Wyoming archive, the Iowa State archive, and the Integrated Global Radiosonde Archive (IGRA). There is no need to download data manually. We can use the siphon library (also developed at Unidata) to request and download these data. Be sure to checkout the documentation on all of siphon's capabilities.

Getting our data

First, we need to create a datetime object that has the time of observation we are looking for. We can then request the data for a specific station. Note that if you provide an invalid time or station where no sounding data are present you will receive an error.

In [1]:
# Create a datetime for our request - notice the times are from laregest (year) to smallest (hour)
from datetime import datetime
request_time = datetime(1999, 5, 3, 12)
In [2]:
# Store the station name in a variable for flexibility and clarity
station = 'OUN'
In [3]:
# Import the Wyoming simple web service and request the data
# Don't worry about a possible warning from Pandas - it's related to our handling of units
from siphon.simplewebservice.wyoming import WyomingUpperAir
df = WyomingUpperAir.request_data(request_time, station)
In [4]:
# Let's see what we got in return
df.head()
Out[4]:
pressure height temperature dewpoint direction speed u_wind v_wind station station_number time latitude longitude elevation
0 966.0 345 18.2 16.9 180.0 15.0 -1.836970e-15 15.000000 OUN 72357 1999-05-03 12:00:00 35.18 -97.44 345.0
1 937.2 610 16.8 15.9 190.0 27.0 4.688501e+00 26.589809 OUN 72357 1999-05-03 12:00:00 35.18 -97.44 345.0
2 925.0 725 16.2 15.5 195.0 31.0 8.023390e+00 29.943701 OUN 72357 1999-05-03 12:00:00 35.18 -97.44 345.0
3 904.6 914 15.1 14.2 205.0 34.0 1.436902e+01 30.814465 OUN 72357 1999-05-03 12:00:00 35.18 -97.44 345.0
4 872.6 1219 13.3 12.1 210.0 38.0 1.900000e+01 32.908965 OUN 72357 1999-05-03 12:00:00 35.18 -97.44 345.0

We got a Pandas dataframe back, which is great. Sadly, Pandas does not play well with units, so we need to attach units and make some other kind of data structure. We've provided a helper function for this - it takes the dataframe with our special .units attribute and returns a dictionary where the keys are column (data series) names and the values are united arrays. This means we can still use the dictionary access syntax and mostly forget that it is not a data frame any longer.

Fist, let's look at the special attribute siphon added:

In [5]:
df.units
Out[5]:
{'pressure': 'hPa',
 'height': 'meter',
 'temperature': 'degC',
 'dewpoint': 'degC',
 'direction': 'degrees',
 'speed': 'knot',
 'u_wind': 'knot',
 'v_wind': 'knot',
 'station': None,
 'station_number': None,
 'time': None,
 'latitude': 'degrees',
 'longitude': 'degrees',
 'elevation': 'meter'}

Now let's import the helper and the units registry from MetPy and get units attached.

In [6]:
from metpy.units import pandas_dataframe_to_unit_arrays, units
sounding = pandas_dataframe_to_unit_arrays(df)
In [7]:
sounding
Out[7]:
{'pressure': array([966. , 937.2, 925. , 904.6, 872.6, 853. , 850. , 836. , 821. ,
        811.6, 782.3, 754.2, 726.9, 700. , 648.9, 624.6, 601.1, 595. ,
        587. , 576. , 555.7, 534.2, 524. , 500. , 473.3, 400. , 384.5,
        358. , 343. , 308.3, 300. , 276. , 273. , 268.5, 250. , 244.2,
        233. , 200. , 191.8, 190. , 174.2, 168. , 151. , 150. , 144. ,
        130.6, 118.4, 105. , 102.2, 100. ,  97.3,  94.7,  80.6,  76. ,
         73.7,  70. ,  61.8,  50. ,  48.6,  44.1,  34.6,  30. ,  29.9,
         26.4,  21.4,  20. ,  16.9,  16.2,  16.1,  15.4,  13.7,  11.3,
         10.7,  10.2,  10. ,   9.9]) <Unit('hectopascal')>,
 'height': array([  345,   610,   725,   914,  1219,  1411,  1441,  1581,  1734,
         1829,  2134,  2438,  2743,  3056,  3658,  3962,  4267,  4348,
         4453,  4600,  4877,  5182,  5331,  5690,  6096,  7340,  7620,
         8124,  8421,  9144,  9330,  9880,  9951, 10058, 10520, 10668,
        10967, 11930, 12192, 12252, 12802, 13032, 13716, 13760, 14021,
        14630, 15240, 15989, 16154, 16290, 16459, 16628, 17621, 17983,
        18167, 18480, 19244, 20550, 20726, 21336, 22860, 23750, 23774,
        24562, 25908, 26350, 27432, 27737, 27766, 28042, 28823, 30112,
        30480, 30785, 30940, 31008]) <Unit('meter')>,
 'temperature': array([ 18.2,  16.8,  16.2,  15.1,  13.3,  12.2,  12.4,  14. ,  14.4,
         13.7,  11.4,   9.1,   6.8,   4.4,  -1.4,  -4.4,  -7.3,  -8.1,
         -7.9,  -7.7,  -8.7,  -9.8, -10.3, -13.5, -17.1, -28.1, -30.7,
        -35.3, -37.1, -43.5, -45.1, -49.9, -50.4, -51.1, -54.1, -55. ,
        -56.7, -57.5, -59.3, -59.7, -55.3, -53.5, -54.2, -54.3, -56.1,
        -59.2, -62.3, -66.1, -62.6, -59.7, -60.7, -61.7, -62.3, -64.7,
        -65.9, -64.5, -62.1, -62.1, -61.8, -60.7, -57.9, -56.3, -56.3,
        -56.3, -51.6, -50.1, -50.6, -50.7, -50.7, -50.1, -48.5, -41.1,
        -41.7, -42.2, -42.5, -42.7]) <Unit('degree_Celsius')>,
 'dewpoint': array([ 16.9,  15.9,  15.5,  14.2,  12.1,  10.8,   8.6,   0. ,  -3.6,
         -4.4,  -6.9,  -9.5, -12. , -14.6, -15.8, -16.4, -16.9, -17.1,
        -27.9, -42.7, -44.1, -45.6, -46.3, -45.5, -47.1, -52.1, -50.4,
        -47.3, -57.1, -57.9, -58.1, -60.9, -61.4, -62.1, -65.1, -65.6,
        -66.7, -70.5, -72.3, -72.7, -72.6, -72.5, -78. , -78.3, -81.1,
        -83.3, -85.4, -88.1, -85.7, -83.7, -84.7, -85.7, -85.3, -87. ,
        -87.9, -87.5, -86.1, -86.1, -85.8, -84.9, -82.6, -81.3, -81.3,
        -81.3, -78.1, -77.1, -77.6, -77.7, -77.7, -77.4, -76.5, -71.1,
        -71.7, -72.2, -72.5, -71.7]) <Unit('degree_Celsius')>,
 'direction': array([180., 190., 195., 205., 210., 214., 215., 215., 215., 215., 230.,
        250., 260., 260., 275., 280., 280., 277., 274., 269., 260., 250.,
        250., 250., 255., 260., 255., 258., 260., 265., 260., 260., 260.,
        260., 260., 260., 261., 265., 265., 270., 280., 270., 240., 245.,
        252., 270., 245., 245., 245., 240., 240., 241., 244., 245., 230.,
        205., 175., 125., 120., 150.,  85.,  50.,  50.,  63.,  85.,  80.,
         75.,  70.,  71.,  85., 113., 157., 170., 310.,  nan,  nan]) <Unit('degree')>,
 'speed': array([15., 27., 31., 34., 38., 33., 32., 29., 26., 24., 18., 16., 20.,
        23., 25., 25., 26., 27., 29., 31., 35., 33., 33., 33., 31., 40.,
        39., 45., 48., 56., 57., 63., 64., 64., 58., 60., 60., 59., 57.,
        54., 37., 39., 43., 41., 35., 21., 22., 26., 27., 21., 20., 19.,
        15., 13., 11.,  8.,  8.,  9., 11.,  6., 15., 14., 14., 18., 25.,
        22., 17., 16., 16., 14., 10.,  3.,  1.,  4., nan, nan]) <Unit('knot')>,
 'u_wind': array([-1.83697020e-15,  4.68850080e+00,  8.02339040e+00,  1.43690209e+01,
         1.90000000e+01,  1.84533658e+01,  1.83544460e+01,  1.66337167e+01,
         1.49129873e+01,  1.37658345e+01,  1.37888000e+01,  1.50350819e+01,
         1.96961551e+01,  2.26505783e+01,  2.49048675e+01,  2.46201938e+01,
         2.56050016e+01,  2.67987461e+01,  2.89293575e+01,  3.09952785e+01,
         3.44682714e+01,  3.10098565e+01,  3.10098565e+01,  3.10098565e+01,
         2.99437006e+01,  3.93923101e+01,  3.76711072e+01,  4.40166420e+01,
         4.72707721e+01,  5.57869031e+01,  5.61340419e+01,  6.20428884e+01,
         6.30276962e+01,  6.30276962e+01,  5.71188497e+01,  5.90884652e+01,
         5.92613004e+01,  5.87754872e+01,  5.67830978e+01,  5.40000000e+01,
         3.64378869e+01,  3.90000000e+01,  3.72390924e+01,  3.71586193e+01,
         3.32869781e+01,  2.10000000e+01,  1.99387713e+01,  2.35640025e+01,
         2.44703102e+01,  1.81865335e+01,  1.73205081e+01,  1.66177744e+01,
         1.34819107e+01,  1.17820012e+01,  8.42648887e+00,  3.38094609e+00,
        -6.97245942e-01, -7.37236840e+00, -9.52627944e+00, -3.00000000e+00,
        -1.49429205e+01, -1.07246222e+01, -1.07246222e+01, -1.60381174e+01,
        -2.49048675e+01, -2.16657706e+01, -1.64207390e+01, -1.50350819e+01,
        -1.51282972e+01, -1.39467258e+01, -9.20504853e+00, -1.17219339e+00,
        -1.73648178e-01,  3.06417777e+00,             nan,             nan]) <Unit('knot')>,
 'v_wind': array([ 1.50000000e+01,  2.65898093e+01,  2.99437006e+01,  3.08144648e+01,
         3.29089653e+01,  2.73582399e+01,  2.62128654e+01,  2.37554093e+01,
         2.12979532e+01,  1.96596491e+01,  1.15701770e+01,  5.47232229e+00,
         3.47296355e+00,  3.99390809e+00, -2.17889357e+00, -4.34120444e+00,
        -4.51485262e+00, -3.29047227e+00, -2.02293774e+00,  5.41024600e-01,
         6.07768622e+00,  1.12866647e+01,  1.12866647e+01,  1.12866647e+01,
         8.02339040e+00,  6.94592711e+00,  1.00939428e+01,  9.35602609e+00,
         8.33511253e+00,  4.88072159e+00,  9.89794613e+00,  1.09398352e+01,
         1.11134834e+01,  1.11134834e+01,  1.00715943e+01,  1.04188907e+01,
         9.38606790e+00,  5.14218882e+00,  4.96787734e+00,  9.91963907e-15,
        -6.42498257e+00,  7.16418378e-15,  2.15000000e+01,  1.73273487e+01,
         1.08155948e+01,  3.85763742e-15,  9.29760176e+00,  1.09880748e+01,
         1.14106931e+01,  1.05000000e+01,  1.00000000e+01,  9.21138278e+00,
         6.57556720e+00,  5.49403740e+00,  7.07066371e+00,  7.25046230e+00,
         7.96955758e+00,  5.16218793e+00,  5.50000000e+00,  5.19615242e+00,
        -1.30733614e+00, -8.99902654e+00, -8.99902654e+00, -8.17182900e+00,
        -2.17889357e+00, -3.82025991e+00, -4.39992377e+00, -5.47232229e+00,
        -5.20909047e+00, -1.22018040e+00,  3.90731128e+00,  2.76151456e+00,
         9.84807753e-01, -2.57115044e+00,             nan,             nan]) <Unit('knot')>,
 'station': array(['OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN', 'OUN',
        'OUN', 'OUN', 'OUN', 'OUN'], dtype=object),
 'station_number': array([72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357, 72357,
        72357, 72357, 72357, 72357]),
 'time': array(['1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000',
        '1999-05-03T12:00:00.000000000', '1999-05-03T12:00:00.000000000'],
       dtype='datetime64[ns]'),
 'latitude': array([35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18, 35.18,
        35.18, 35.18, 35.18, 35.18]) <Unit('degree')>,
 'longitude': array([-97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44, -97.44,
        -97.44, -97.44, -97.44, -97.44]) <Unit('degree')>,
 'elevation': array([345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345., 345.,
        345., 345., 345., 345., 345., 345., 345., 345., 345., 345.]) <Unit('meter')>}

Top


2.Make a Skew-T

Now that we have data, we can actually start making our Skew-T Log-P diagram. This consists of:

  • Import matplotlib
  • Importing the SkewT object
  • Creating a figure
  • Creating a SkewT object based upon that figure
  • Plotting our data
In [8]:
import matplotlib.pyplot as plt
from metpy.plots import SkewT
%matplotlib inline
In [9]:
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(10, 10))
skew = SkewT(fig)
In [10]:
# Plot the data using normal plotting functions, all of the transforms
# happen in the background!
skew.plot(sounding['pressure'], sounding['temperature'], color='tab:red')
skew.ax.set_ylim(1050,100)
skew.ax.set_xlim(-50,20)
# Redisplay the figure
fig
Out[10]:
In [11]:
# Plot a isotherm using axvline (axis vertical line)
skew.ax.axvline(0 * units.degC, color='cyan', linestyle='--')

# Redisplay the figure
fig
Out[11]:
EXERCISE Part 1:
  • Download your own data using the Wyoming upper-air archive. Have a look at the documentation to help get started.
  • Attach units using the unit helper.
In [12]:
# Import the Wyoming simple web service upper air object
# YOUR CODE GOES HERE
In [13]:
# Create the datetime and station variables you'll need
# YOUR CODE GOES HERE
In [14]:
# Make the request for the data - assign it to a dataframe called my_df
# YOUR CODE GOES HERE
In [15]:
# Attach units to the data - assign it to the variable my_sounding
# YOUR CODE GOES HERE
SOLUTION
In [16]:
# %load solutions/skewt_get_data.py

# Cell content replaced by load magic replacement.
# Import the Wyoming simple web service upper air object
from siphon.simplewebservice.wyoming import WyomingUpperAir

# Create the datetime and station variables you'll need
request_time = datetime(2011, 4, 14, 18)
station = 'OUN'

# Make the request for the data
my_df = WyomingUpperAir.request_data(request_time, station)

# Attach units to the data
my_sounding = pandas_dataframe_to_unit_arrays(my_df)
EXERCISE Part 2:
  • Make a figure and SkewT object.
  • Plot the temperature and dewpoint in red and green lines.
  • Set the axis limits to sensible limits with set_xlim and set_ylim.
In [17]:
# Make a figure called my_fig

# Make a SkewT object called my_skew

# Plot the temperature and dewpoint
SOLUTION
In [18]:
# %load solutions/skewt_make_figure.py

# Cell content replaced by load magic replacement.
# Make a figure
my_fig = plt.figure(figsize=(10, 10))

# Make a SkewT object
my_skew = SkewT(my_fig)

# Plot the temperature and dewpoint
my_skew.plot(my_sounding['pressure'], my_sounding['temperature'], linewidth=2, color='tab:red')
my_skew.plot(my_sounding['pressure'], my_sounding['dewpoint'], linewidth=2, color='tab:blue')
Out[18]:
[<matplotlib.lines.Line2D at 0x7f37214c7d50>]
EXERCISE Part 3:
  • Plot wind barbs using the plot_barbs method of the SkewT object.
  • Add the fiducial lines for dry adiabats, moist adiabats, and mixing ratio lines using the plot_dry_adiabats(), plot_moist_adiabats(), plot_mixing_lines() functions.
In [19]:
# Plot wind barbs

# Add dry adiabats

# Add moist adiabats

# Add mixing ratio lines

# Redisplay figure
SOLUTION
In [20]:
# %load solutions/skewt_wind_fiducials.py

# Cell content replaced by load magic replacement.
# Plot wind barbs
my_skew.plot_barbs(my_sounding['pressure'], my_sounding['u_wind'], my_sounding['v_wind'])

# Add dry adiabats
my_skew.plot_dry_adiabats()

# Add moist adiabats
my_skew.plot_moist_adiabats()

# Add mixing ratio lines
my_skew.plot_mixing_lines()

# Redisplay figure
my_fig
Out[20]:

Top


3.Thermodynamics

Using MetPy's calculations functions we can calculate thermodynamic paramters like LCL, LFC, EL, CAPE, and CIN. Let's start off with the LCL.

In [21]:
# Add the dewpoint line to our plot we were working with
skew.plot(sounding['pressure'], sounding['dewpoint'], linewidth=2, color='tab:blue')
Out[21]:
[<matplotlib.lines.Line2D at 0x7f3721433d50>]
In [22]:
# Get our basic plot we were working with ready to plot new things on

# Set some good axis limits
skew.ax.set_xlim(-60, 30)
skew.ax.set_ylim(1000, 100)

# Add relevant fiducial lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()

fig
Out[22]:
In [23]:
import metpy.calc as mpcalc
In [24]:
lcl_pressure, lcl_temperature = mpcalc.lcl(sounding['pressure'][0],
                                           sounding['temperature'][0],
                                           sounding['dewpoint'][0])
print(lcl_pressure, lcl_temperature)
947.5050980075163 hectopascal 16.5956981693401 degree_Celsius

We can this as a point on our sounding using the scatter method.

In [25]:
skew.ax.axhline(y=lcl_pressure, color='k', linewidth=0.75)
fig
Out[25]:

We can also calculate the ideal parcel profile and plot it.

In [26]:
sounding['profile'] = mpcalc.parcel_profile(sounding['pressure'], sounding['temperature'][0], sounding['dewpoint'][0])
print(sounding['profile'])
[291.34999999999997 289.3328742971223 288.83644733895886 287.98694490856474 286.6022061473723 285.720203249939 285.5828020058605 284.93283458953607 284.21985805296583 283.76393842487647 282.29490892845496 280.81233547092194 279.2954220523558 277.71888551880875 274.46643913578544 272.78321395298457 271.05879097547745 270.5943305023465 269.97405171792434 269.0996727167148 267.41643753922017 265.5263779708061 264.58763137213907 262.2615281871119 259.4590386032502 250.32395058674635 248.0653598159943 243.88985430707118 241.33663505392926 234.85386523891404 233.17624886311145 228.03032505204118 227.35516500400587 226.3286939486981 221.93062666264655 220.49074933321572 217.6240032208801 208.4615550485471 206.00199651687453 205.4514147025197 200.44233152694054 198.38445151202512 192.44148432697258 192.0770470318521 189.8527261196877 184.6325200127664 179.53384878058887 173.4798302074525 172.14577055358134 171.0791302167014 169.74689612106994 168.43879338225148 160.85851076807472 158.1810248694957 156.79866017263024 154.5087128843014 149.10687664352014 140.3506548398376 139.21677124549166 135.40622798809386 126.34089665263664 121.29636599855316 121.1807420338651 116.94734799407591 110.13966195597962 108.031610947383 102.95772415074347 101.72118885642519 101.5414425967192 100.26034635785595 96.96592701235348 91.77585859574747 90.35673956525376 89.1300463179209 88.62732979907548 88.37327405149297] kelvin
In [27]:
# Plot the profile
skew.plot(sounding['pressure'], sounding['profile'], color='black')

# Redisplay the figure
fig
Out[27]:
EXERCISE:
  • Calculate the LFC and EL for the sounding.
  • Plot them as horizontal line markers.
In [28]:
# Calculate the LFC
# YOUR CODE GOES HERE

# Calculate the EL
# YOUR CODE GOES HERE
In [29]:
# Create a new figure and SkewT object
fig = plt.figure(figsize=(10, 10))
skew = SkewT(fig)

# Plot the profile and data
skew.plot(sounding['pressure'], sounding['profile'], color='black')
skew.plot(sounding['pressure'], sounding['temperature'], color='tab:red')
skew.plot(sounding['pressure'], sounding['dewpoint'], color='tab:blue')

# Plot the LCL, LFC, and EL as horizontal lines
# YOUR CODE GOES HERE

# Set axis limits
skew.ax.set_xlim(-60, 30)
skew.ax.set_ylim(1000, 100)

# Add fiducial lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()
Out[29]:
<matplotlib.collections.LineCollection at 0x7f372146e090>
SOLUTION
In [30]:
# %load solutions/skewt_thermo.py

# Cell content replaced by load magic replacement.
# Get data for the sounding
df = WyomingUpperAir.request_data(datetime(1999, 5, 3, 12), 'OUN')

# Calculate the ideal surface parcel path
sounding['profile'] = mpcalc.parcel_profile(sounding['pressure'],
                                            sounding['temperature'][0],
                                            sounding['dewpoint'][0]).to('degC')

# Calculate the LCL
lcl_pressure, lcl_temperature = mpcalc.lcl(sounding['pressure'][0],
                                           sounding['temperature'][0],
                                           sounding['dewpoint'][0])

# Calculate the LFC
lfc_pressure, lfc_temperature = mpcalc.lfc(sounding['pressure'],
                                           sounding['temperature'],
                                           sounding['dewpoint'])

# Calculate the EL
el_pressure, el_temperature = mpcalc.el(sounding['pressure'],
                                        sounding['temperature'],
                                        sounding['dewpoint'])

# Create a new figure and SkewT object
fig = plt.figure(figsize=(10, 10))
skew = SkewT(fig)

# Plot the profile and data
skew.plot(sounding['pressure'], sounding['profile'], color='black')
skew.plot(sounding['pressure'], sounding['temperature'], color='tab:red')
skew.plot(sounding['pressure'], sounding['dewpoint'], color='tab:blue')

# Plot the LCL, LFC, and EL as horizontal markers
if lcl_pressure:
    skew.ax.plot(lcl_temperature, lcl_pressure, marker="_", color='orange', markersize=30, markeredgewidth=3)
    
if lfc_pressure:
    skew.ax.plot(lfc_temperature, lfc_pressure, marker="_", color='brown', markersize=30, markeredgewidth=3)
    
if el_pressure:
    skew.ax.plot(el_temperature, el_pressure, marker="_", color='blue', markersize=30, markeredgewidth=3)

# Set axis limits
skew.ax.set_xlim(-60, 30)
skew.ax.set_ylim(1000, 100)

# Add fiducial lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()
Out[30]:
<matplotlib.collections.LineCollection at 0x7f372121e8d0>
BONUS:
  • Use the function surface_based_cape_cin in the MetPy calculations module to calculate the CAPE and CIN of this sounding. Print out the values.
  • Using the methods shade_cape and shade_cin on the SkewT object, shade the areas representing CAPE and CIN.
In [31]:
# Calculate surface based cape/cin
# YOUR CODE GOES HERE

# Print CAPE and CIN
# YOUR CODE GOES HERE
In [32]:
# Shade CAPE
# YOUR CODE GOES HERE

# Shade CIN
# YOUR CODE GOES HERE

# Redisplay the figure
fig
Out[32]:
SOLUTION
In [33]:
# %load solutions/skewt_cape_cin.py

# Cell content replaced by load magic replacement.
# Calculate surface based cape/cin
surface_cape, surface_cin = mpcalc.surface_based_cape_cin(sounding['pressure'],
                                                          sounding['temperature'],
                                                          sounding['dewpoint'])

# Print CAPE and CIN
print('CAPE: {}\tCIN: {}'.format(surface_cape, surface_cin))

# Shade CAPE
skew.shade_cape(sounding['pressure'],
                sounding['temperature'],
                sounding['profile'])

# Shade CIN
skew.shade_cin(sounding['pressure'],
               sounding['temperature'],
               sounding['profile'])

# Redisplay the figure
fig
CAPE: 1173.364886838819 joule / kilogram	CIN: -9.505951217484121 joule / kilogram
Out[33]:

Top


4.Plotting a Hodograph

Hodographs are a great way to look at wind shear - they are created by drawing wind vectors, all starting at the origin of a plot, and the connecting the vector tips. They are often thought of as a polar plot where the range rings (lines of constant radius) represent speed and the angle representes the compass angle of the wind.

In MetPy we can create a hodograph in a similar way to a skew-T - we create a hodograph object and attach it to an axes.

In [34]:
# Import the hodograph class
from metpy.plots import Hodograph
In [35]:
# Make a figure and axis
fig, ax = plt.subplots(1, 1, figsize=(6, 6))

# Create a hodograph
h = Hodograph(ax, component_range=60.)

# Add "range rings" to the plot
h.add_grid(increment=20)

# Plot the wind data
h.plot(sounding['u_wind'], sounding['v_wind'], color='tab:red')
Out[35]:
[<matplotlib.lines.Line2D at 0x7f37210a63d0>]

We can even add wind vectors, which is helpful for learning/teaching hodographs.

In [36]:
# Add vectors
h.wind_vectors(sounding['u_wind'], sounding['v_wind'])

# Redisplay figure
fig
Out[36]:

This is great, but we generally don't care about wind shear for the entire sounding. Let's say we want to view it in the lowest 10km of the atmosphere. We can do this with the powerful, but complex get_layer function. Let's get a subset of the u-wind, v-wind, and windspeed.

In [37]:
(_, u_trimmed, v_trimmed,
 speed_trimmed, height_trimmed) = mpcalc.get_layer(sounding['pressure'],
                                                   sounding['u_wind'],
                                                   sounding['v_wind'],
                                                   sounding['speed'],
                                                   sounding['height'],
                                                   heights=sounding['height'],
                                                   depth=10 * units.km)

Let's make the same hodograph again, but we'll also color the line by the value of the windspeed and we'll use the trimmed data we just created.

In [38]:
from metpy.plots import colortables
import numpy as np
In [39]:
fig, ax = plt.subplots(1, 1, figsize=(6, 6))

h = Hodograph(ax, component_range=60.)
h.add_grid(increment=20)

norm, cmap = colortables.get_with_range('ir_rgbv', np.nanmin(speed_trimmed.m),
                                        np.nanmax(speed_trimmed.m))

h.plot_colormapped(u_trimmed, v_trimmed, speed_trimmed,
                   cmap=cmap, norm=norm)
h.wind_vectors(u_trimmed[::3], v_trimmed[::3])
Out[39]:
<matplotlib.quiver.Quiver at 0x7f3720773210>
EXERCISE Part 1: In this exercise you'll create a hodograph that is colored by a variable that is not displayed - height above ground level. We generally wouldn't want to color this in a continuous fashion, so we'll make a hodograph that is segmented by height.
  • Make a variable to hold the height above ground level (subtract the surface height from the heights in the sounding).
  • Make an list of boundary values that we'll use to segment the hodograph from 0-1, 1-3, 3-5, and 5-8 km. (Hint the array should have one more value than the number of segments desired.)
  • Make a list of colors for each segment.
In [40]:
# Calculate the height above ground level (AGL)
# YOUR CODE GOES HERE
In [41]:
# Make an array of segment boundaries - don't forget units!
# YOUR CODE GOES HERE
In [42]:
# Make a list of colors for the segments
# YOUR CODE GOES HERE
SOLUTION
In [43]:
# %load solutions/hodograph_preprocessing.py

# Cell content replaced by load magic replacement.
# Calculate the height above ground level (AGL)
sounding['height_agl'] = sounding['height'] - sounding['height'][0]

# Make an array of segment boundaries - don't forget units!
boundaries = [0, 1, 3, 5, 8] * units.km

# Make a list of colors for the segments
colors = ['tab:red', 'tab:green', 'tab:blue', 'tab:olive']
EXERCISE Part 2:
  • Make a new figure and hodograph object.
  • Using the bounds and colors keyword arguments to plot_colormapped create the segmented hodograph.
  • BONUS: Add a colorbar!
In [44]:
# Create figure/axis
# YOUR CODE GOES HERE

# Create a hodograph object/fiducial lines
# YOUR CODE GOES HERE

# Plot the data
# YOUR CODE GOES HERE

# BONUS - add a colorbar
# YOUR CODE GOES HERE
SOLUTION
In [45]:
# %load solutions/hodograph_segmented.py

# Cell content replaced by load magic replacement.
# Create figure/axis
fig, ax = plt.subplots(1, 1, figsize=(6, 6))

# Create a hodograph object/fiducial lines
h = Hodograph(ax, component_range=60.)
h.add_grid(increment=20)

# Plot the data
l = h.plot_colormapped(sounding['u_wind'],
                       sounding['v_wind'],
                       sounding['height_agl'],
                       bounds=boundaries, colors=colors)

# BONUS - add a colorbar
plt.colorbar(l)
/home/travis/miniconda/envs/unidata/lib/python3.7/site-packages/metpy/plots/skewt.py:919: MetpyDeprecationWarning: The use of "bounds" as a parameter has been deprecated in favor of "intervals",
  'favor of "intervals",', metpyDeprecation)
Out[45]:
<matplotlib.colorbar.Colorbar at 0x7f3720792c90>

Top


5.Advanced Layout

This section is meant to show you some fancy matplotlib to make nice Skew-T/Hodograph combinations. It's a good starting place to make your custom plot for your needs.

In [46]:
# Get the data we want
df = WyomingUpperAir.request_data(datetime(1998, 10, 4, 0), 'OUN')
sounding = pandas_dataframe_to_unit_arrays(df)
In [47]:
# Calculate thermodynamics
lcl_pressure, lcl_temperature = mpcalc.lcl(sounding['pressure'][0],
                                           sounding['temperature'][0],
                                           sounding['dewpoint'][0])

lfc_pressure, lfc_temperature = mpcalc.lfc(sounding['pressure'],
                                           sounding['temperature'],
                                           sounding['dewpoint'])

el_pressure, el_temperature = mpcalc.el(sounding['pressure'],
                                        sounding['temperature'],
                                        sounding['dewpoint'])

parcel_profile = mpcalc.parcel_profile(sounding['pressure'],
                                       sounding['temperature'][0],
                                       sounding['dewpoint'][0])
In [48]:
# Some new imports
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from metpy.plots import add_metpy_logo
In [49]:
# Make the plot
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(9, 9))
add_metpy_logo(fig, 630, 80, size='large')

# Grid for plots
gs = gridspec.GridSpec(3, 3)
skew = SkewT(fig, rotation=45, subplot=gs[:, :2])

# Plot the sounding using normal plotting functions, in this case using
# log scaling in Y, as dictated by the typical meteorological plot
skew.plot(sounding['pressure'], sounding['temperature'], 'tab:red')
skew.plot(sounding['pressure'], sounding['dewpoint'], 'tab:green')
skew.plot(sounding['pressure'], parcel_profile, 'k')

# Mask barbs to be below 100 hPa only
mask = sounding['pressure'] >= 100 * units.hPa
skew.plot_barbs(sounding['pressure'][mask], sounding['u_wind'][mask], sounding['v_wind'][mask])
skew.ax.set_ylim(1000, 100)

# Add the relevant special lines
skew.plot_dry_adiabats()
skew.plot_moist_adiabats()
skew.plot_mixing_lines()

# Shade areas
skew.shade_cin(sounding['pressure'], sounding['temperature'], parcel_profile)
skew.shade_cape(sounding['pressure'], sounding['temperature'], parcel_profile)

# Good bounds for aspect ratio
skew.ax.set_xlim(-25, 30)

if lcl_pressure:
    skew.ax.plot(lcl_temperature, lcl_pressure, marker="_", color='tab:orange', markersize=30, markeredgewidth=3)
    
if lfc_pressure:
    skew.ax.plot(lfc_temperature, lfc_pressure, marker="_", color='tab:brown', markersize=30, markeredgewidth=3)
    
if el_pressure:
    skew.ax.plot(el_temperature, el_pressure, marker="_", color='tab:blue', markersize=30, markeredgewidth=3)

# Create a hodograph
agl = sounding['height'] - sounding['height'][0]
mask = agl <= 10 * units.km
intervals = np.array([0, 1, 3, 5, 8]) * units.km
colors = ['tab:red', 'tab:green', 'tab:blue', 'tab:olive']
ax = fig.add_subplot(gs[0, -1])
h = Hodograph(ax, component_range=30.)
h.add_grid(increment=10)
h.plot_colormapped(sounding['u_wind'][mask], sounding['v_wind'][mask], agl[mask], intervals=intervals, colors=colors)
Out[49]:
<matplotlib.collections.LineCollection at 0x7f372059bf90>

Top