Public data APIs#

How do we access amazing data? The answer is data APIs.

What is an API?#

API stands for Application Programming Interface and it serves as a data transmitter between two different pieces of software. The term has historically been used to describe any sort of connectivity interface to an application.

Today, the term API typically takes an additional meaning:

  • Modern APIs adhere to certain standards (typically HTTP and REST)

  • They are treated like products

  • They often adhere to high security standards

We usually have two interfaces for Software:

  • UI (User Interface) - humans talking to computers

  • API - computers talking to computers

Types of APIs#

By availability#

  • Open and public APIs are available to everyone, allowing anyone to access or perform operations on public datasets.

  • Internal APIs are used in-house by developers.

  • Partner APIs are a form of semi-open API where access is granted under certain conditions determined by the publisher.

By structure#

APIs also differ in architecture. The most popular API architectures are:

  • JSON-RPC and XML-RPC RPC stands for Remote Procedure Call and is a protocol for data transmission in JSON or XML format.

  • REST stands for representational state transfer. It’s a software architectural style that provides a set of recommendations for web development.

  • SOAP stands for Simple Object Access Protocol and is a definition of API protocols and standards.

  • GraphQL Graph Query Language, used for accessing highly interconnected data (e.g. GitHub)

Accessing a Public Data API with Python#

In order to retrieve data from an API we will combine the request library and the JSON library.

When we want to receive data from the API we first make a request.

To do so in python, we need to install the requests library.

import requests

Example: NASA’s APOD API#

Let’s test out one of NASA’s open APIs:

demo_key = "DEMO_KEY"

url = "https://api.nasa.gov/planetary/apod"

response = requests.get(url, params={"api_key": demo_key, "date": "2023-10-16"})
response
200 https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=2023-10-16
headers:   Access-Control-Allow-Origin: *
  Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining
  Age: 0
  Connection: keep-alive
  Content-Encoding: gzip
  Content-Type: application/json
  Date: Wed, 25 Oct 2023 09:47:42 GMT
  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  Transfer-Encoding: chunked
  Vary: Accept-Encoding
  Via: http/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
  X-Api-Umbrella-Request-Id: cbcotovkqsd1bir13org
  X-Cache: MISS
  X-Content-Type-Options: nosniff
  X-Frame-Options: DENY
  X-Ratelimit-Limit: 40
  X-Ratelimit-Remaining: 39
  X-Vcap-Request-Id: 076d6fe2-3d57-441b-6a5d-cf6737433b3c
  X-XSS-Protection: 1; mode=block

body (application/json):
{'copyright': '\nJerry Zhang (left), \nBaolong Chen (photographer) & Amber Zhang (right)\n',
 'date': '2023-10-16',
 'explanation': 'She knew everything but the question. She was well aware that there would be a complete annular eclipse of the Sun visible from their driving destination: Lake Abert in Oregon.  She knew that the next ring-of-fire eclipse would occur in the USA only in 16 more years, making this a rare photographic opportunity.  She was comfortable with the plan: that she and her boyfriend would appear in front of the eclipse in silhouette, sometimes alone, and sometimes together.  She knew that the annular phase of this eclipse would last only a few minutes and she helped in the many hours of planning.  She could see their friend who set up the camera about 400 meters away at the bottom of a ridge.  What she didn\'t know was the question she would be asked. But she did know the answer: "yes".   Album: Selected eclipse images sent in to APOD',
 'hdurl': 'https://apod.nasa.gov/apod/image/2310/AnnularProposal_Zhang_3500.jpg',
 'media_type': 'image',
 'service_version': 'v1',
 'title': 'Eclipse Rings',
 'url': 'https://apod.nasa.gov/apod/image/2310/AnnularProposal_Zhang_960.jpg'}
import json

data = response.json()
from IPython.display import HTML, Image, display

display(HTML(response.json()["explanation"]))
Image(url=data["url"], width=600)
She knew everything but the question. She was well aware that there would be a complete annular eclipse of the Sun visible from their driving destination: Lake Abert in Oregon. She knew that the next ring-of-fire eclipse would occur in the USA only in 16 more years, making this a rare photographic opportunity. She was comfortable with the plan: that she and her boyfriend would appear in front of the eclipse in silhouette, sometimes alone, and sometimes together. She knew that the annular phase of this eclipse would last only a few minutes and she helped in the many hours of planning. She could see their friend who set up the camera about 400 meters away at the bottom of a ridge. What she didn't know was the question she would be asked. But she did know the answer: "yes". Album: Selected eclipse images sent in to APOD

Key concepts:#

Get and post

The two most common requests we make are get and post:

  • GET is used for viewing (without changing)

  • POST is used for changing, and sometimes viewing data

Response codes

We can check the response code to see if our request was successfull.

if 200 <= response.status_code < 300:
    print("Request succeeded!")
else:
    print(f"Uh oh, we got response code {response.status_code}...")
Request succeeded!

Here are some common status codes:

(defined by HTTP)

Code

Status

Description

200

OK

The request was successfully completed

400

Bad request

The request was invalid.

401

Unauthorized

The request did not include an authentication token or the authentication token was expired.

403

Forbidden

The client did not have permission to access the requested resource.

404

Not Found

The requested resource was not found.

405

Method Not Allowed

Often the wrong HTTP VERB, e.g. POST to a URL that only allows GET

500

Internal Server Error

The request was not completed due to an internal error on the server side.

503

Service unavailable

The server was unavailable.

Headers

We can communicate metadata (like who we are) to the API by means of a header.

The server also communicates back to us with a header:

dict(response.headers)
{'Date': 'Wed, 25 Oct 2023 09:47:42 GMT',
 'Content-Type': 'application/json',
 'Transfer-Encoding': 'chunked',
 'Connection': 'keep-alive',
 'Access-Control-Allow-Origin': '*',
 'Access-Control-Expose-Headers': 'X-RateLimit-Limit, X-RateLimit-Remaining',
 'Age': '0',
 'Content-Encoding': 'gzip',
 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
 'Vary': 'Accept-Encoding',
 'Via': 'http/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])',
 'X-Api-Umbrella-Request-Id': 'cbcotovkqsd1bir13org',
 'X-Cache': 'MISS',
 'X-Ratelimit-Limit': '40',
 'X-Ratelimit-Remaining': '39',
 'X-Vcap-Request-Id': '076d6fe2-3d57-441b-6a5d-cf6737433b3c',
 'X-Frame-Options': 'DENY',
 'X-Content-Type-Options': 'nosniff',
 'X-XSS-Protection': '1; mode=block'}

Etiquette

When you access a public data API you are expected to follow the playing rules of the API. Therefore you should always start by checking the documentation on the website for the API.

Exercise#

Make a request to a public API

For examples:

url = "https://www.api.toys/api/dice_roll"
r = requests.get(url, params={"sides": 6, "rolls": 3})
r.json()
{'dice': 'd6', 'rolls': [5, 4, 4]}
url = "https://meowfacts.herokuapp.com/"
r = requests.get(url)

data = r.json()
data
{'data': ["Cats can get tapeworms from eating fleas. These worms live inside the cat forever, or until they are removed with medication. They reproduce by shedding a link from the end of their long bodies. This link crawls out the cat's anus, and sheds hundreds of eggs. These eggs are injested by flea larvae, and the cycles continues. Humans may get these tapeworms too, but only if they eat infected fleas. Cats with tapeworms should be dewormed by a veterinarian."]}
facts = data["data"]
for fact in facts:
    print(fact)
Cats can get tapeworms from eating fleas. These worms live inside the cat forever, or until they are removed with medication. They reproduce by shedding a link from the end of their long bodies. This link crawls out the cat's anus, and sheds hundreds of eggs. These eggs are injested by flea larvae, and the cycles continues. Humans may get these tapeworms too, but only if they eat infected fleas. Cats with tapeworms should be dewormed by a veterinarian.
requests.get?
Signature: requests.get(url, params=None, **kwargs)
Docstring:
Sends a GET request.

:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
    in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
File:      ~/conda/envs/3110/lib/python3.11/site-packages/requests/api.py
Type:      function

Data formats#

The actual data typically gets returned in a JSON, XML or CSV file.

CSV#

A Comma-Separated Values file is a delimited text file that uses a comma to separate values. It typically stores tabular data.

Example: FHI provides data about the number of confirmed COVID cases in Norway as a .csv file.

JSON#

JSON is the most common way of sending data back and forth in APIs.

A JavaScript Object Notation (JSON) file encodes data structures so that they are easy to read for machines and somewhat easy to read for humans.

JSON is a text file or string that follows the JavaScript object syntax. Most programming languages will have the ability to read (parse) and generate JSON files.

The json library in Python has two main functions:

  • json.dumps() Takes in a python object and converts it (dumps it) to a string

  • json.loads() Takes in a JSON string and converts it (loads it) to a Python object.

which will convert from/to the following formats

JSON

Python

object

dict

array

list

string

str

number

int or float

true

True

false

False

null

None

Let’s look at what NASA gave us

# Load data using response.json
response.json?
Signature: response.json(**kwargs)
Docstring:
Returns the json-encoded content of a response, if any.

:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises requests.exceptions.JSONDecodeError: If the response body does not
    contain valid json.
File:      ~/conda/envs/3110/lib/python3.11/site-packages/requests/models.py
Type:      method
data = response.json()

print(f"response.json() is a {type(data)}:")

data
response.json() is a <class 'dict'>:
{'copyright': '\nJerry Zhang (left), \nBaolong Chen (photographer) & Amber Zhang (right)\n',
 'date': '2023-10-16',
 'explanation': 'She knew everything but the question. She was well aware that there would be a complete annular eclipse of the Sun visible from their driving destination: Lake Abert in Oregon.  She knew that the next ring-of-fire eclipse would occur in the USA only in 16 more years, making this a rare photographic opportunity.  She was comfortable with the plan: that she and her boyfriend would appear in front of the eclipse in silhouette, sometimes alone, and sometimes together.  She knew that the annular phase of this eclipse would last only a few minutes and she helped in the many hours of planning.  She could see their friend who set up the camera about 400 meters away at the bottom of a ridge.  What she didn\'t know was the question she would be asked. But she did know the answer: "yes".   Album: Selected eclipse images sent in to APOD',
 'hdurl': 'https://apod.nasa.gov/apod/image/2310/AnnularProposal_Zhang_3500.jpg',
 'media_type': 'image',
 'service_version': 'v1',
 'title': 'Eclipse Rings',
 'url': 'https://apod.nasa.gov/apod/image/2310/AnnularProposal_Zhang_960.jpg'}

Exercise#

  1. Make a get request from the NASA Mars Rover API to get pictures from Mars.

  2. Specifiy the camera viewpoint in parameters

# Get satellite image from Houston
parameters = {
    "sol": 1500,
    "api_key": "DEMO_KEY",
    "page": 1,
}

url = "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos"
r = requests.get(url, params=parameters)

from IPython.display import JSON

JSON(r.json())
<IPython.core.display.JSON object>

Entur API#

The NASA is a good ‘hello world’ example of APIs but it does not quite showcase the usefullness of APIs.

Entur has APIs is an example of an open API, that features both an open source code and APIs for stops, real-time data, mobility trends etc.

The following snippet is adapted from ruterstop by stigok:

stop_id = 5926

__version__ = "0.5.1"

ENTUR_CLIENT_ID = __version__

ENTUR_GRAPHQL_ENDPOINT = "https://api.entur.io/journey-planner/v2/graphql"

ENTUR_GRAPHQL_QUERY = """
{
  stopPlace(id: "NSR:StopPlace:$STOP_ID") {
    name
    estimatedCalls(timeRange: 72100, numberOfDepartures: 20) {
      expectedArrivalTime
      realtime
      destinationDisplay {
        frontText
      }
      serviceJourney {
        directionType
        line {
          publicCode
        }
      }
    }
  }
}
"""

headers = {
    "Accept": "application/json",
    "ET-Client-Name": "UIO:IN3110 - ingeborggjerde",
    "ET-Client-Id": ENTUR_CLIENT_ID,
}

qry = ENTUR_GRAPHQL_QUERY.replace("$STOP_ID", str(stop_id))
res = requests.post(
    ENTUR_GRAPHQL_ENDPOINT,
    headers=headers,
    timeout=5,
    json=dict(query=qry, variables={}),
)
JSON(res.json())
<IPython.core.display.JSON object>

Getting public API data into pandas#

Let’s check the weather

Yr has an API: https://developer.yr.no

They ask you to identify yourself in the User-Agent header: https://developer.yr.no/doc/TermsOfService/

url = "https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=51.5&lon=0"
s = requests.Session()
s.headers[
    "User-Agent"
] = "uio-in3110 https://github.com/uio-in3110/uio-in3110.github.io"
r = s.get(url)
r
200 https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=51.5&lon=0
headers:   Accept-Ranges: bytes
  Access-Control-Allow-Headers: Origin
  Access-Control-Allow-Methods: GET
  Access-Control-Allow-Origin: *
  Age: 0
  Connection: keep-alive
  Content-Encoding: gzip
  Content-Length: 2988
  Content-Type: application/json
  Date: Wed, 25 Oct 2023 09:47:45 GMT
  Expires: Wed, 25 Oct 2023 10:17:49 GMT
  Last-Modified: Wed, 25 Oct 2023 09:47:44 GMT
  Server: nginx/1.18.0 (Ubuntu)
  Vary: Accept, Accept-Encoding
  Via: 1.1 varnish (Varnish/7.0)
  X-Backend-Host: b_157_249_76_16_loc
  X-Varnish: 99576577

body (application/json):
{'type': 'Feature',
 'geometry': {'type': 'Point', 'coordinates': [0, 51.5, 4]},
 'properties': {'meta': {'updated_at': '2023-10-25T09:38:21Z',
   'units': {'air_pressure_at_sea_level': 'hPa',
    'air_temperature': 'celsius',
    'cloud_area_fraction': '%',
    'precipitation_amount': 'mm',
    'relative_humidity': '%',
    'wind_from_direction': 'degrees',
    'wind_speed': 'm/s'}},
  'timeseries': [{'time': '2023-10-25T09:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.1,
       'air_temperature': 10.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 89.2,
       'wind_from_direction': 33.3,
       'wind_speed': 4.0}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-25T10:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.1,
       'air_temperature': 11.3,
       'cloud_area_fraction': 94.5,
       'relative_humidity': 86.6,
       'wind_from_direction': 33.0,
       'wind_speed': 4.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-25T11:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.2,
       'air_temperature': 11.5,
       'cloud_area_fraction': 89.8,
       'relative_humidity': 85.7,
       'wind_from_direction': 6.9,
       'wind_speed': 4.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.4,
       'air_temperature': 12.3,
       'cloud_area_fraction': 75.8,
       'relative_humidity': 81.7,
       'wind_from_direction': 11.4,
       'wind_speed': 3.4}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T13:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.3,
       'air_temperature': 13.7,
       'cloud_area_fraction': 42.2,
       'relative_humidity': 77.0,
       'wind_from_direction': 22.9,
       'wind_speed': 3.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T14:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.7,
       'air_temperature': 13.8,
       'cloud_area_fraction': 98.4,
       'relative_humidity': 75.3,
       'wind_from_direction': 0.8,
       'wind_speed': 3.6}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T15:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.9,
       'air_temperature': 13.2,
       'cloud_area_fraction': 17.2,
       'relative_humidity': 75.1,
       'wind_from_direction': 358.6,
       'wind_speed': 3.0}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T16:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.2,
       'air_temperature': 12.4,
       'cloud_area_fraction': 6.2,
       'relative_humidity': 78.6,
       'wind_from_direction': 343.4,
       'wind_speed': 1.8}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_1_hours': {'summary': {'symbol_code': 'clearsky_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T17:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.5,
       'air_temperature': 9.4,
       'cloud_area_fraction': 89.1,
       'relative_humidity': 89.7,
       'wind_from_direction': 322.6,
       'wind_speed': 2.1}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.7,
       'air_temperature': 8.6,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 91.9,
       'wind_from_direction': 322.2,
       'wind_speed': 2.5}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T19:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.9,
       'air_temperature': 8.2,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 93.7,
       'wind_from_direction': 317.6,
       'wind_speed': 2.1}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrain'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-25T20:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.8,
       'air_temperature': 9.5,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 90.5,
       'wind_from_direction': 258.4,
       'wind_speed': 0.9}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrain'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-25T21:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.7,
       'air_temperature': 8.4,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 95.7,
       'wind_from_direction': 117.6,
       'wind_speed': 1.7}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrain'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.2}}}},
   {'time': '2023-10-25T22:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.7,
       'air_temperature': 8.0,
       'cloud_area_fraction': 96.1,
       'relative_humidity': 96.1,
       'wind_from_direction': 123.6,
       'wind_speed': 1.9}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.4}}}},
   {'time': '2023-10-25T23:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.2,
       'air_temperature': 7.8,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 96.0,
       'wind_from_direction': 112.6,
       'wind_speed': 2.4}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.4}}}},
   {'time': '2023-10-26T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.6,
       'air_temperature': 8.4,
       'cloud_area_fraction': 45.3,
       'relative_humidity': 94.2,
       'wind_from_direction': 113.7,
       'wind_speed': 3.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.5}}}},
   {'time': '2023-10-26T01:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.1,
       'air_temperature': 8.4,
       'cloud_area_fraction': 96.1,
       'relative_humidity': 94.4,
       'wind_from_direction': 107.0,
       'wind_speed': 3.2}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 1.0}}}},
   {'time': '2023-10-26T02:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 994.4,
       'air_temperature': 9.7,
       'cloud_area_fraction': 99.2,
       'relative_humidity': 93.2,
       'wind_from_direction': 110.9,
       'wind_speed': 3.5}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'lightrain'},
      'details': {'precipitation_amount': 0.1}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 1.2}}}},
   {'time': '2023-10-26T03:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 993.3,
       'air_temperature': 9.9,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 94.3,
       'wind_from_direction': 110.3,
       'wind_speed': 3.4}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'lightrain'},
      'details': {'precipitation_amount': 0.2}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 1.7}}}},
   {'time': '2023-10-26T04:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 992.5,
       'air_temperature': 10.1,
       'cloud_area_fraction': 95.3,
       'relative_humidity': 95.2,
       'wind_from_direction': 108.5,
       'wind_speed': 3.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.6}}}},
   {'time': '2023-10-26T05:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 992.2,
       'air_temperature': 10.2,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 95.4,
       'wind_from_direction': 122.4,
       'wind_speed': 3.2}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.5}}}},
   {'time': '2023-10-26T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.9,
       'air_temperature': 10.6,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 92.6,
       'wind_from_direction': 150.7,
       'wind_speed': 3.3}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 0.5}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.5}}}},
   {'time': '2023-10-26T07:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.5,
       'air_temperature': 10.3,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 91.8,
       'wind_from_direction': 134.2,
       'wind_speed': 3.3}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 0.3}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.0}}}},
   {'time': '2023-10-26T08:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.4,
       'air_temperature': 10.8,
       'cloud_area_fraction': 70.3,
       'relative_humidity': 90.3,
       'wind_from_direction': 125.0,
       'wind_speed': 4.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 0.6}},
     'next_6_hours': {'summary': {'symbol_code': 'lightrainshowers_day'},
      'details': {'precipitation_amount': 0.9}}}},
   {'time': '2023-10-26T09:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.3,
       'air_temperature': 12.1,
       'cloud_area_fraction': 58.6,
       'relative_humidity': 86.2,
       'wind_from_direction': 143.7,
       'wind_speed': 4.3}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-26T10:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.3,
       'air_temperature': 13.7,
       'cloud_area_fraction': 28.9,
       'relative_humidity': 82.5,
       'wind_from_direction': 224.1,
       'wind_speed': 5.0}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-26T11:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.2,
       'air_temperature': 14.3,
       'cloud_area_fraction': 71.1,
       'relative_humidity': 78.2,
       'wind_from_direction': 242.7,
       'wind_speed': 4.8}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-26T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.9,
       'air_temperature': 15.3,
       'cloud_area_fraction': 78.9,
       'relative_humidity': 73.8,
       'wind_from_direction': 243.8,
       'wind_speed': 4.2}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-26T13:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 15.7,
       'cloud_area_fraction': 87.5,
       'relative_humidity': 71.0,
       'wind_from_direction': 235.5,
       'wind_speed': 4.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'lightrain'},
      'details': {'precipitation_amount': 0.2}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-26T14:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.4,
       'air_temperature': 14.5,
       'cloud_area_fraction': 96.1,
       'relative_humidity': 73.9,
       'wind_from_direction': 223.0,
       'wind_speed': 4.3}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-26T15:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.1,
       'air_temperature': 14.5,
       'cloud_area_fraction': 99.2,
       'relative_humidity': 73.7,
       'wind_from_direction': 212.6,
       'wind_speed': 3.6}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_night'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-26T16:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.1,
       'air_temperature': 13.3,
       'cloud_area_fraction': 99.2,
       'relative_humidity': 82.7,
       'wind_from_direction': 200.7,
       'wind_speed': 2.8}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-26T17:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.1,
       'air_temperature': 11.8,
       'cloud_area_fraction': 43.0,
       'relative_humidity': 88.9,
       'wind_from_direction': 198.1,
       'wind_speed': 2.7}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-26T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.4,
       'air_temperature': 11.4,
       'cloud_area_fraction': 46.1,
       'relative_humidity': 90.2,
       'wind_from_direction': 203.8,
       'wind_speed': 2.8}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-26T19:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 10.6,
       'cloud_area_fraction': 20.3,
       'relative_humidity': 93.4,
       'wind_from_direction': 208.0,
       'wind_speed': 3.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-26T20:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 9.9,
       'cloud_area_fraction': 5.5,
       'relative_humidity': 95.4,
       'wind_from_direction': 209.0,
       'wind_speed': 2.9}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_night'}},
     'next_1_hours': {'summary': {'symbol_code': 'clearsky_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-26T21:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 9.1,
       'cloud_area_fraction': 2.3,
       'relative_humidity': 95.9,
       'wind_from_direction': 206.6,
       'wind_speed': 2.9}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_night'}},
     'next_1_hours': {'summary': {'symbol_code': 'clearsky_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-26T22:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.6,
       'air_temperature': 9.2,
       'cloud_area_fraction': 0.0,
       'relative_humidity': 95.1,
       'wind_from_direction': 206.9,
       'wind_speed': 3.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'clearsky_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-26T23:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.8,
       'air_temperature': 9.5,
       'cloud_area_fraction': 33.6,
       'relative_humidity': 93.8,
       'wind_from_direction': 207.1,
       'wind_speed': 3.3}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.8,
       'air_temperature': 9.5,
       'cloud_area_fraction': 96.9,
       'relative_humidity': 94.0,
       'wind_from_direction': 206.3,
       'wind_speed': 3.4}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T01:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.7,
       'air_temperature': 9.7,
       'cloud_area_fraction': 98.4,
       'relative_humidity': 94.4,
       'wind_from_direction': 207.9,
       'wind_speed': 3.3}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T02:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.6,
       'air_temperature': 9.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 94.6,
       'wind_from_direction': 211.9,
       'wind_speed': 3.2}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T03:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.2,
       'air_temperature': 9.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 94.7,
       'wind_from_direction': 219.2,
       'wind_speed': 3.3}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T04:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.4,
       'air_temperature': 9.8,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 95.3,
       'wind_from_direction': 222.1,
       'wind_speed': 3.7}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T05:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 9.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 95.5,
       'wind_from_direction': 226.8,
       'wind_speed': 3.4}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.3}}}},
   {'time': '2023-10-27T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.7,
       'air_temperature': 9.6,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 95.4,
       'wind_from_direction': 222.6,
       'wind_speed': 3.6}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.4}}}},
   {'time': '2023-10-27T07:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.8,
       'air_temperature': 9.6,
       'cloud_area_fraction': 83.6,
       'relative_humidity': 95.2,
       'wind_from_direction': 216.1,
       'wind_speed': 3.4}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'lightrainshowers_day'},
      'details': {'precipitation_amount': 0.6}}}},
   {'time': '2023-10-27T08:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.2,
       'air_temperature': 10.6,
       'cloud_area_fraction': 43.0,
       'relative_humidity': 92.4,
       'wind_from_direction': 211.1,
       'wind_speed': 3.9}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.9}}}},
   {'time': '2023-10-27T09:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.4,
       'air_temperature': 12.4,
       'cloud_area_fraction': 18.7,
       'relative_humidity': 83.2,
       'wind_from_direction': 221.2,
       'wind_speed': 4.6}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 5.8}}}},
   {'time': '2023-10-27T10:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.4,
       'air_temperature': 13.4,
       'cloud_area_fraction': 28.1,
       'relative_humidity': 77.5,
       'wind_from_direction': 223.0,
       'wind_speed': 5.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 0.3}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 5.8}}}},
   {'time': '2023-10-27T11:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.7,
       'air_temperature': 13.2,
       'cloud_area_fraction': 39.8,
       'relative_humidity': 77.4,
       'wind_from_direction': 221.4,
       'wind_speed': 5.5}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 5.5}}}},
   {'time': '2023-10-27T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.4,
       'air_temperature': 14.2,
       'cloud_area_fraction': 46.9,
       'relative_humidity': 74.1,
       'wind_from_direction': 215.5,
       'wind_speed': 6.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_1_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 0.3}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 5.5}}}},
   {'time': '2023-10-27T13:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.3,
       'air_temperature': 14.0,
       'cloud_area_fraction': 94.5,
       'relative_humidity': 70.6,
       'wind_from_direction': 224.4,
       'wind_speed': 6.4}},
     'next_1_hours': {'summary': {'symbol_code': 'heavyrain'},
      'details': {'precipitation_amount': 1.3}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 5.2}}}},
   {'time': '2023-10-27T14:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.2,
       'air_temperature': 13.5,
       'cloud_area_fraction': 53.1,
       'relative_humidity': 74.0,
       'wind_from_direction': 218.1,
       'wind_speed': 5.4}},
     'next_1_hours': {'summary': {'symbol_code': 'heavyrainshowers_day'},
      'details': {'precipitation_amount': 3.9}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 3.9}}}},
   {'time': '2023-10-27T15:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.5,
       'air_temperature': 12.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 77.1,
       'wind_from_direction': 234.5,
       'wind_speed': 6.0}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T16:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.5,
       'air_temperature': 12.1,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 81.7,
       'wind_from_direction': 219.1,
       'wind_speed': 4.5}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T17:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.6,
       'air_temperature': 11.2,
       'cloud_area_fraction': 96.9,
       'relative_humidity': 85.9,
       'wind_from_direction': 214.9,
       'wind_speed': 4.7}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.8,
       'air_temperature': 11.0,
       'cloud_area_fraction': 39.1,
       'relative_humidity': 87.3,
       'wind_from_direction': 215.1,
       'wind_speed': 4.7}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T19:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.9,
       'air_temperature': 10.5,
       'cloud_area_fraction': 28.9,
       'relative_humidity': 88.1,
       'wind_from_direction': 208.9,
       'wind_speed': 4.8}},
     'next_1_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T20:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 992.0,
       'air_temperature': 10.2,
       'cloud_area_fraction': 93.7,
       'relative_humidity': 86.3,
       'wind_from_direction': 204.5,
       'wind_speed': 5.4}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T21:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.9,
       'air_temperature': 10.1,
       'cloud_area_fraction': 78.9,
       'relative_humidity': 88.2,
       'wind_from_direction': 204.7,
       'wind_speed': 4.9}},
     'next_1_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T22:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.9,
       'air_temperature': 10.0,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 90.8,
       'wind_from_direction': 200.0,
       'wind_speed': 4.8}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-27T23:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.5,
       'air_temperature': 10.2,
       'cloud_area_fraction': 99.2,
       'relative_humidity': 90.4,
       'wind_from_direction': 194.5,
       'wind_speed': 4.8}},
     'next_1_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-28T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.0,
       'air_temperature': 10.4,
       'cloud_area_fraction': 99.2,
       'relative_humidity': 90.0,
       'wind_from_direction': 187.2,
       'wind_speed': 4.6}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-28T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 988.9,
       'air_temperature': 10.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 89.5,
       'wind_from_direction': 208.6,
       'wind_speed': 5.1}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-28T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.7,
       'air_temperature': 14.8,
       'cloud_area_fraction': 25.0,
       'relative_humidity': 68.2,
       'wind_from_direction': 217.0,
       'wind_speed': 7.3}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_day'},
      'details': {'precipitation_amount': 0.5}}}},
   {'time': '2023-10-28T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 989.2,
       'air_temperature': 12.3,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 81.6,
       'wind_from_direction': 154.9,
       'wind_speed': 4.7}},
     'next_12_hours': {'summary': {'symbol_code': 'rain'}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrain'},
      'details': {'precipitation_amount': 8.9}}}},
   {'time': '2023-10-29T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 983.0,
       'air_temperature': 12.1,
       'cloud_area_fraction': 95.3,
       'relative_humidity': 90.7,
       'wind_from_direction': 197.8,
       'wind_speed': 6.3}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 1.0}}}},
   {'time': '2023-10-29T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 980.1,
       'air_temperature': 11.3,
       'cloud_area_fraction': 96.9,
       'relative_humidity': 86.3,
       'wind_from_direction': 218.0,
       'wind_speed': 9.0}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-29T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 987.1,
       'air_temperature': 14.7,
       'cloud_area_fraction': 21.1,
       'relative_humidity': 64.0,
       'wind_from_direction': 228.6,
       'wind_speed': 11.8}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-29T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 990.5,
       'air_temperature': 11.1,
       'cloud_area_fraction': 82.0,
       'relative_humidity': 77.5,
       'wind_from_direction': 219.4,
       'wind_speed': 6.1}},
     'next_12_hours': {'summary': {'symbol_code': 'fair_night'}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-30T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 995.3,
       'air_temperature': 8.9,
       'cloud_area_fraction': 4.7,
       'relative_humidity': 89.5,
       'wind_from_direction': 233.5,
       'wind_speed': 4.5}},
     'next_12_hours': {'summary': {'symbol_code': 'fair_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'clearsky_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-30T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 997.5,
       'air_temperature': 6.8,
       'cloud_area_fraction': 5.5,
       'relative_humidity': 92.8,
       'wind_from_direction': 214.1,
       'wind_speed': 2.1}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'clearsky_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-30T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 997.0,
       'air_temperature': 12.5,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 77.4,
       'wind_from_direction': 83.2,
       'wind_speed': 1.8}},
     'next_12_hours': {'summary': {'symbol_code': 'rain'}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 4.2}}}},
   {'time': '2023-10-30T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 993.8,
       'air_temperature': 9.8,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 91.2,
       'wind_from_direction': 25.0,
       'wind_speed': 4.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrain'}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrain'},
      'details': {'precipitation_amount': 8.2}}}},
   {'time': '2023-10-31T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 997.0,
       'air_temperature': 8.4,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 91.0,
       'wind_from_direction': 12.4,
       'wind_speed': 3.5}},
     'next_12_hours': {'summary': {'symbol_code': 'cloudy'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.1}}}},
   {'time': '2023-10-31T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1000.2,
       'air_temperature': 4.5,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 91.2,
       'wind_from_direction': 359.2,
       'wind_speed': 2.6}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-31T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1002.8,
       'air_temperature': 8.9,
       'cloud_area_fraction': 45.3,
       'relative_humidity': 71.0,
       'wind_from_direction': 16.0,
       'wind_speed': 3.9}},
     'next_12_hours': {'summary': {'symbol_code': 'fair_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'partlycloudy_day'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-10-31T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1006.8,
       'air_temperature': 4.8,
       'cloud_area_fraction': 0.0,
       'relative_humidity': 86.1,
       'wind_from_direction': 333.6,
       'wind_speed': 1.9}},
     'next_12_hours': {'summary': {'symbol_code': 'partlycloudy_night'}},
     'next_6_hours': {'summary': {'symbol_code': 'clearsky_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-11-01T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1010.0,
       'air_temperature': 1.2,
       'cloud_area_fraction': 32.8,
       'relative_humidity': 98.9,
       'wind_from_direction': 302.6,
       'wind_speed': 1.4}},
     'next_12_hours': {'summary': {'symbol_code': 'lightsleetshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'fair_night'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-11-01T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1009.0,
       'air_temperature': 1.1,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 99.4,
       'wind_from_direction': 90.1,
       'wind_speed': 2.4}},
     'next_12_hours': {'summary': {'symbol_code': 'rain'}},
     'next_6_hours': {'summary': {'symbol_code': 'sleet'},
      'details': {'precipitation_amount': 3.7}}}},
   {'time': '2023-11-01T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 1002.5,
       'air_temperature': 8.7,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 92.1,
       'wind_from_direction': 105.5,
       'wind_speed': 6.5}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 3.4}}}},
   {'time': '2023-11-01T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 996.4,
       'air_temperature': 12.2,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 88.8,
       'wind_from_direction': 155.6,
       'wind_speed': 6.0}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'rain'},
      'details': {'precipitation_amount': 2.7}}}},
   {'time': '2023-11-02T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 991.7,
       'air_temperature': 12.1,
       'cloud_area_fraction': 39.1,
       'relative_humidity': 90.6,
       'wind_from_direction': 185.0,
       'wind_speed': 5.9}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_night'},
      'details': {'precipitation_amount': 2.3}}}},
   {'time': '2023-11-02T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 986.0,
       'air_temperature': 11.5,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 88.3,
       'wind_from_direction': 148.4,
       'wind_speed': 5.0}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'lightrain'},
      'details': {'precipitation_amount': 0.7}}}},
   {'time': '2023-11-02T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 982.2,
       'air_temperature': 13.3,
       'cloud_area_fraction': 56.2,
       'relative_humidity': 77.7,
       'wind_from_direction': 266.0,
       'wind_speed': 5.3}},
     'next_12_hours': {'summary': {'symbol_code': 'lightrain'}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 1.0}}}},
   {'time': '2023-11-02T18:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 986.6,
       'air_temperature': 10.3,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 85.9,
       'wind_from_direction': 247.2,
       'wind_speed': 7.3}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'cloudy'},
      'details': {'precipitation_amount': 0.0}}}},
   {'time': '2023-11-03T00:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 981.3,
       'air_temperature': 7.3,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 75.7,
       'wind_from_direction': 162.0,
       'wind_speed': 2.7}},
     'next_12_hours': {'summary': {'symbol_code': 'rainshowers_day'}},
     'next_6_hours': {'summary': {'symbol_code': 'heavyrain'},
      'details': {'precipitation_amount': 11.6}}}},
   {'time': '2023-11-03T06:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 958.5,
       'air_temperature': 10.6,
       'cloud_area_fraction': 17.2,
       'relative_humidity': 79.5,
       'wind_from_direction': 198.8,
       'wind_speed': 7.7}},
     'next_6_hours': {'summary': {'symbol_code': 'rainshowers_day'},
      'details': {'precipitation_amount': 3.0}}}},
   {'time': '2023-11-03T12:00:00Z',
    'data': {'instant': {'details': {'air_pressure_at_sea_level': 960.5,
       'air_temperature': 7.9,
       'cloud_area_fraction': 100.0,
       'relative_humidity': 82.4,
       'wind_from_direction': 288.9,
       'wind_speed': 10.5}}}}]}}
data = r.json()
record = data["properties"]["timeseries"][0]
JSON(data)
<IPython.core.display.JSON object>
from IPython.display import JSON

JSON(record)
<IPython.core.display.JSON object>

Exercise#

Tidy data is easier to work with for tools like pands.

  • Every column is a variable.

  • Every row is an observation.

  • Every cell is a single value.

Task: tidy a record dict from Yr:

  • turn nested dictionary into one-level dictionary of keys, values

  • add prefixes for nested keys that may conflict, e.g.

next(iter(record["data"].items()))
('instant',
 {'details': {'air_pressure_at_sea_level': 995.1,
   'air_temperature': 10.7,
   'cloud_area_fraction': 100.0,
   'relative_humidity': 89.2,
   'wind_from_direction': 33.3,
   'wind_speed': 4.0}})
def tidy_format(record):
    tidy_dict = {"time": record["time"]}
    for section, sub_dict in record["data"].items():
        for _, fields in sub_dict.items():
            for key, value in fields.items():
                if section == "instant":
                    tidy_key = key
                else:
                    tidy_key = f"{section}_{key}"
                tidy_dict[tidy_key] = value
    return tidy_dict


tidy_format(record)
{'time': '2023-10-25T09:00:00Z',
 'air_pressure_at_sea_level': 995.1,
 'air_temperature': 10.7,
 'cloud_area_fraction': 100.0,
 'relative_humidity': 89.2,
 'wind_from_direction': 33.3,
 'wind_speed': 4.0,
 'next_12_hours_symbol_code': 'partlycloudy_day',
 'next_1_hours_symbol_code': 'cloudy',
 'next_1_hours_precipitation_amount': 0.0,
 'next_6_hours_symbol_code': 'partlycloudy_day',
 'next_6_hours_precipitation_amount': 0.1}
tidy_records = [tidy_format(record) for record in r.json()["properties"]["timeseries"]]
import pandas as pd
df = pd.DataFrame.from_dict(tidy_records)
df
time air_pressure_at_sea_level air_temperature cloud_area_fraction relative_humidity wind_from_direction wind_speed next_12_hours_symbol_code next_1_hours_symbol_code next_1_hours_precipitation_amount next_6_hours_symbol_code next_6_hours_precipitation_amount
0 2023-10-25T09:00:00Z 995.1 10.7 100.0 89.2 33.3 4.0 partlycloudy_day cloudy 0.0 partlycloudy_day 0.1
1 2023-10-25T10:00:00Z 995.1 11.3 94.5 86.6 33.0 4.1 partlycloudy_day cloudy 0.0 partlycloudy_day 0.1
2 2023-10-25T11:00:00Z 995.2 11.5 89.8 85.7 6.9 4.1 partlycloudy_day cloudy 0.0 partlycloudy_day 0.0
3 2023-10-25T12:00:00Z 995.4 12.3 75.8 81.7 11.4 3.4 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0
4 2023-10-25T13:00:00Z 995.3 13.7 42.2 77.0 22.9 3.1 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0
... ... ... ... ... ... ... ... ... ... ... ... ...
85 2023-11-02T12:00:00Z 982.2 13.3 56.2 77.7 266.0 5.3 lightrain NaN NaN rainshowers_day 1.0
86 2023-11-02T18:00:00Z 986.6 10.3 100.0 85.9 247.2 7.3 rainshowers_day NaN NaN cloudy 0.0
87 2023-11-03T00:00:00Z 981.3 7.3 100.0 75.7 162.0 2.7 rainshowers_day NaN NaN heavyrain 11.6
88 2023-11-03T06:00:00Z 958.5 10.6 17.2 79.5 198.8 7.7 NaN NaN NaN rainshowers_day 3.0
89 2023-11-03T12:00:00Z 960.5 7.9 100.0 82.4 288.9 10.5 NaN NaN NaN NaN NaN

90 rows × 12 columns

df = pd.DataFrame.from_dict(tidy_records)
df["time"] = pd.to_datetime(df["time"])
df = df.set_index("time")
df
air_pressure_at_sea_level air_temperature cloud_area_fraction relative_humidity wind_from_direction wind_speed next_12_hours_symbol_code next_1_hours_symbol_code next_1_hours_precipitation_amount next_6_hours_symbol_code next_6_hours_precipitation_amount
time
2023-10-25 09:00:00+00:00 995.1 10.7 100.0 89.2 33.3 4.0 partlycloudy_day cloudy 0.0 partlycloudy_day 0.1
2023-10-25 10:00:00+00:00 995.1 11.3 94.5 86.6 33.0 4.1 partlycloudy_day cloudy 0.0 partlycloudy_day 0.1
2023-10-25 11:00:00+00:00 995.2 11.5 89.8 85.7 6.9 4.1 partlycloudy_day cloudy 0.0 partlycloudy_day 0.0
2023-10-25 12:00:00+00:00 995.4 12.3 75.8 81.7 11.4 3.4 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0
2023-10-25 13:00:00+00:00 995.3 13.7 42.2 77.0 22.9 3.1 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0
... ... ... ... ... ... ... ... ... ... ... ...
2023-11-02 12:00:00+00:00 982.2 13.3 56.2 77.7 266.0 5.3 lightrain NaN NaN rainshowers_day 1.0
2023-11-02 18:00:00+00:00 986.6 10.3 100.0 85.9 247.2 7.3 rainshowers_day NaN NaN cloudy 0.0
2023-11-03 00:00:00+00:00 981.3 7.3 100.0 75.7 162.0 2.7 rainshowers_day NaN NaN heavyrain 11.6
2023-11-03 06:00:00+00:00 958.5 10.6 17.2 79.5 198.8 7.7 NaN NaN NaN rainshowers_day 3.0
2023-11-03 12:00:00+00:00 960.5 7.9 100.0 82.4 288.9 10.5 NaN NaN NaN NaN NaN

90 rows × 11 columns

df.air_temperature.plot(grid=True)
<Axes: xlabel='time'>
../../_images/bb649f71f20a4857c2278c41b2f76441f77fe41e505bcdf06a42d3a8e01cb744.png
df.cloud_area_fraction.hist()
<Axes: >
../../_images/1fe3b2c36e341e8291e2b2da1cd799af32dbfdc8441c7d0573cc1f0516a2150d.png

Another API:

The weather API expects latitude, longitude, but we have location names.

There’s an API for that!

r = requests.get(
    "https://geocode.maps.co/search",
    params={"city": "Bergen", "country": "Norway"},
)
r.json()
[{'place_id': 286402934,
  'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
  'powered_by': 'Map Maker: https://maps.co',
  'osm_type': 'relation',
  'osm_id': 404159,
  'boundingbox': ['60.1760905', '60.5360925', '5.1445788', '5.6867918'],
  'lat': '60.3943055',
  'lon': '5.3259192',
  'display_name': 'Bergen, Vestland, Norway',
  'class': 'boundary',
  'type': 'administrative',
  'importance': 0.8412391232119534},
 {'place_id': 62960991,
  'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright',
  'powered_by': 'Map Maker: https://maps.co',
  'osm_type': 'node',
  'osm_id': 5638206557,
  'boundingbox': ['59.6009016', '59.6209016', '8.9104283', '8.9304283'],
  'lat': '59.6109016',
  'lon': '8.9204283',
  'display_name': 'Bergen, Hjartdal, Vestfold og Telemark, 3692, Norway',
  'class': 'place',
  'type': 'farm',
  'importance': 0.42}]

Write a function to locate a city, returning latitude, longitude as floats

def locate_city(city: str, country: str = "Norway") -> tuple[float, float]:
    """return (lat, lon) for a city"""
    r = requests.get(
        "https://geocode.maps.co/search",
        params={"city": city, "country": country},
    )

    matches = r.json()
    if len(matches) < 1:
        raise ValueError(f"No match for {city}, {country}")

    location = matches[0]
    return (float(location["lat"]), float(location["lon"]))
locate_city("Oslo")
(59.9133301, 10.7389701)
locate_city("Bergen")
(60.3943055, 5.3259192)

Exercise:

write a function to get the city forcast, given name and country

  1. look up lat, lon from name, country

  2. get forecast

  3. add ‘city’ column

def city_forecast(city: str, country: str = "Norway") -> pd.DataFrame:
    """Given a city name, return a DataFrame of the weather forecast

    1. resolves gps for city
    2. fetches forecast
    3. tidies data
    4. wraps in DataFrame
    """
    lat, lon = locate_city(city, country)
    url = "https://api.met.no/weatherapi/locationforecast/2.0/compact"
    r = s.get(url, params={"lat": lat, "lon": lon})
    tidy_records = [
        tidy_format(record) for record in r.json()["properties"]["timeseries"]
    ]
    df = pd.DataFrame.from_dict(tidy_records)
    df["time"] = pd.to_datetime(df["time"])
    df["city"] = city
    return df


oslo = city_forecast("Oslo")
oslo
time air_pressure_at_sea_level air_temperature cloud_area_fraction relative_humidity wind_from_direction wind_speed next_12_hours_symbol_code next_1_hours_symbol_code next_1_hours_precipitation_amount next_6_hours_symbol_code next_6_hours_precipitation_amount city
0 2023-10-25 09:00:00+00:00 1015.2 2.7 91.2 74.1 46.8 4.7 partlycloudy_day cloudy 0.0 partlycloudy_day 0.0 Oslo
1 2023-10-25 10:00:00+00:00 1015.0 3.5 79.0 71.3 48.5 4.5 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
2 2023-10-25 11:00:00+00:00 1014.2 4.3 68.0 68.1 47.8 4.8 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
3 2023-10-25 12:00:00+00:00 1013.8 5.0 73.0 65.7 52.4 4.8 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
4 2023-10-25 13:00:00+00:00 1013.5 5.3 68.1 64.7 52.7 5.2 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
... ... ... ... ... ... ... ... ... ... ... ... ... ...
83 2023-11-03 06:00:00+00:00 1002.4 4.0 100.0 93.2 56.0 4.1 lightrain NaN NaN cloudy 0.0 Oslo
84 2023-11-03 12:00:00+00:00 999.3 3.8 100.0 89.4 57.0 4.6 rain NaN NaN rain 2.7 Oslo
85 2023-11-03 18:00:00+00:00 996.7 5.1 100.0 94.9 61.9 4.4 lightrain NaN NaN rain 2.9 Oslo
86 2023-11-04 00:00:00+00:00 995.8 4.4 100.0 95.2 54.7 4.1 NaN NaN NaN cloudy 0.0 Oslo
87 2023-11-04 06:00:00+00:00 993.7 4.2 100.0 91.5 55.2 4.6 NaN NaN NaN NaN NaN Oslo

88 rows × 13 columns

forecasts = pd.concat(
    city_forecast(city) for city in ("Oslo", "Bergen", " Tromsø", "Trondheim")
)
forecasts
time air_pressure_at_sea_level air_temperature cloud_area_fraction relative_humidity wind_from_direction wind_speed next_12_hours_symbol_code next_1_hours_symbol_code next_1_hours_precipitation_amount next_6_hours_symbol_code next_6_hours_precipitation_amount city
0 2023-10-25 09:00:00+00:00 1015.2 2.7 91.2 74.1 46.8 4.7 partlycloudy_day cloudy 0.0 partlycloudy_day 0.0 Oslo
1 2023-10-25 10:00:00+00:00 1015.0 3.5 79.0 71.3 48.5 4.5 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
2 2023-10-25 11:00:00+00:00 1014.2 4.3 68.0 68.1 47.8 4.8 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
3 2023-10-25 12:00:00+00:00 1013.8 5.0 73.0 65.7 52.4 4.8 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
4 2023-10-25 13:00:00+00:00 1013.5 5.3 68.1 64.7 52.7 5.2 partlycloudy_day partlycloudy_day 0.0 partlycloudy_day 0.0 Oslo
... ... ... ... ... ... ... ... ... ... ... ... ... ...
83 2023-11-03 06:00:00+00:00 1001.9 3.6 100.0 70.9 128.2 4.2 cloudy NaN NaN cloudy 0.0 Trondheim
84 2023-11-03 12:00:00+00:00 994.9 5.6 98.8 67.3 116.6 4.2 cloudy NaN NaN cloudy 0.0 Trondheim
85 2023-11-03 18:00:00+00:00 997.6 4.5 95.7 74.1 129.1 4.2 cloudy NaN NaN cloudy 0.0 Trondheim
86 2023-11-04 00:00:00+00:00 995.8 4.3 100.0 74.1 135.3 4.1 NaN NaN NaN cloudy 0.0 Trondheim
87 2023-11-04 06:00:00+00:00 995.0 3.8 100.0 76.2 120.3 4.1 NaN NaN NaN NaN NaN Trondheim

352 rows × 13 columns

forecasts.groupby(["time", "city"])
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x16cd7da90>
forecasts.groupby(["time", "city"]).air_temperature
<pandas.core.groupby.generic.SeriesGroupBy object at 0x16cda3790>
forecasts.groupby(["time", "city"]).air_temperature.first()
time                       city     
2023-10-25 09:00:00+00:00   Tromsø      -0.7
                           Bergen       10.7
                           Oslo          2.7
                           Trondheim     2.1
2023-10-25 10:00:00+00:00   Tromsø       0.5
                                        ... 
2023-11-04 00:00:00+00:00  Trondheim     4.3
2023-11-04 06:00:00+00:00   Tromsø      -0.9
                           Bergen        6.3
                           Oslo          4.2
                           Trondheim     3.8
Name: air_temperature, Length: 352, dtype: float64
forecasts.groupby(["time", "city"]).air_temperature.first().unstack()
city Tromsø Bergen Oslo Trondheim
time
2023-10-25 09:00:00+00:00 -0.7 10.7 2.7 2.1
2023-10-25 10:00:00+00:00 0.5 11.6 3.5 3.5
2023-10-25 11:00:00+00:00 1.4 12.4 4.3 4.9
2023-10-25 12:00:00+00:00 1.7 13.2 5.0 5.9
2023-10-25 13:00:00+00:00 1.7 13.5 5.3 6.3
... ... ... ... ...
2023-11-03 06:00:00+00:00 -0.6 6.0 4.0 3.6
2023-11-03 12:00:00+00:00 0.8 8.3 3.8 5.6
2023-11-03 18:00:00+00:00 -0.5 6.8 5.1 4.5
2023-11-04 00:00:00+00:00 -1.3 6.3 4.4 4.3
2023-11-04 06:00:00+00:00 -0.9 6.3 4.2 3.8

88 rows × 4 columns

forecasts.groupby(["time", "city"]).air_temperature.first().unstack().plot()
<Axes: xlabel='time'>
../../_images/108b4bbee7280ae2f4ece293a46b73a52ae63de6cad81ddaad5030fe003def2a.png
forecasts.groupby(["time", "city"]).cloud_area_fraction.first().unstack().plot()
<Axes: xlabel='time'>
../../_images/9ed3e9b2e71d18ebc5401352fe3aad776f510496f4f4006443e745edc06ac6c0.png
forecasts.groupby(
    ["next_1_hours_symbol_code", "city"]
).next_1_hours_symbol_code.count().unstack().plot(kind="bar")
<Axes: xlabel='next_1_hours_symbol_code'>
../../_images/c646d9d032bc7873c9b2c55c03ee3b389f41e97f9d76164da415bd0636700c34.png

I don’t really care about day/night. How do we remove that?

forecasts["next_1_hours_symbol_code"].value_counts()
next_1_hours_symbol_code
clearsky_night        54
clearsky_day          42
cloudy                40
partlycloudy_day      32
partlycloudy_night    31
fair_night            14
rain                   6
fair_day               5
lightrain              4
Name: count, dtype: int64
forecasts["next_1_hours_symbol_code"].str.split("_", n=1, expand=True).value_counts()
0             1    
clearsky      night    54
              day      42
partlycloudy  day      32
              night    31
fair          night    14
              day       5
Name: count, dtype: int64
forecasts["next_1_hours_symbol_code"].str.split("_", n=1, expand=True)
0 1
0 cloudy None
1 partlycloudy day
2 partlycloudy day
3 partlycloudy day
4 partlycloudy day
... ... ...
83 NaN NaN
84 NaN NaN
85 NaN NaN
86 NaN NaN
87 NaN NaN

352 rows × 2 columns

for n in (1, 6, 12):
    forecasts[f"next_{n}_hours_symbol_code_clean"] = forecasts[
        f"next_{n}_hours_symbol_code"
    ].str.split("_", n=1, expand=True)[0]
forecasts.groupby(
    ["next_1_hours_symbol_code_clean", "city"]
).next_1_hours_symbol_code.count().unstack().plot(kind="bar")
<Axes: xlabel='next_1_hours_symbol_code_clean'>
../../_images/91dc80c0d111c09a85cabc095f3a2e113b1d5873fb182b5b3bba591090083dbc.png
forecasts.groupby(
    ["next_6_hours_symbol_code_clean", "city"]
).next_1_hours_symbol_code.count().unstack().plot(kind="bar")
<Axes: xlabel='next_6_hours_symbol_code_clean'>
../../_images/dc967d2083f1054e607e18e45bf13ef3bfede647282e449154898fbd427fcc38.png
forecasts.groupby(
    ["time", "city"]
).next_6_hours_precipitation_amount.first().unstack().dropna().plot()
<Axes: xlabel='time'>
../../_images/c259c848bd5573cbf5d569938ae1db5da94b1301a85dc6a595322c510f1c1a44.png