From 1b273b8c66ea22c039768550bce50519ef6b0839 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 30 Jan 2025 16:23:29 +0000 Subject: [PATCH] source commit: 96d0f44b14dd6522f6f44d134ba777d8728acc6d --- CODE_OF_CONDUCT.md | 13 + LICENSE.md | 79 ++++ accessing-remote-data.md | 579 ++++++++++++++++++++++++++++++ automated-testing.md | 25 ++ big-data.md | 25 ++ config.yaml | 87 +++++ data-environments.md | 25 ++ data-validation.md | 25 ++ debugging.md | 25 ++ documentation.md | 25 ++ fig/excelheader.png | Bin 0 -> 83948 bytes hot-takes.md | 25 ++ index.md | 21 ++ instructor-notes.md | 5 + introduction.md | 28 ++ learner-profiles.md | 115 ++++++ links.md | 10 + md5sum.txt | 25 ++ modularization.md | 25 ++ other-options.md | 27 ++ plotting.md | 25 ++ python-environments.md | 25 ++ reference.md | 8 + scraping-data.md | 30 ++ setup.md | 54 +++ working-with-diverse-filetypes.md | 493 +++++++++++++++++++++++++ 26 files changed, 1824 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE.md create mode 100644 accessing-remote-data.md create mode 100644 automated-testing.md create mode 100644 big-data.md create mode 100644 config.yaml create mode 100644 data-environments.md create mode 100644 data-validation.md create mode 100644 debugging.md create mode 100644 documentation.md create mode 100644 fig/excelheader.png create mode 100644 hot-takes.md create mode 100644 index.md create mode 100644 instructor-notes.md create mode 100644 introduction.md create mode 100644 learner-profiles.md create mode 100644 links.md create mode 100644 md5sum.txt create mode 100644 modularization.md create mode 100644 other-options.md create mode 100644 plotting.md create mode 100644 python-environments.md create mode 100644 reference.md create mode 100644 scraping-data.md create mode 100644 setup.md create mode 100644 working-with-diverse-filetypes.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f19b804 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,13 @@ +--- +title: "Contributor Code of Conduct" +--- + +As contributors and maintainers of this project, +we pledge to follow the [The Carpentries Code of Conduct][coc]. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by following our [reporting guidelines][coc-reporting]. + + +[coc-reporting]: https://docs.carpentries.org/topic_folders/policies/incident-reporting.html +[coc]: https://docs.carpentries.org/topic_folders/policies/code-of-conduct.html diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..7632871 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,79 @@ +--- +title: "Licenses" +--- + +## Instructional Material + +All Carpentries (Software Carpentry, Data Carpentry, and Library Carpentry) +instructional material is made available under the [Creative Commons +Attribution license][cc-by-human]. The following is a human-readable summary of +(and not a substitute for) the [full legal text of the CC BY 4.0 +license][cc-by-legal]. + +You are free: + +- to **Share**---copy and redistribute the material in any medium or format +- to **Adapt**---remix, transform, and build upon the material + +for any purpose, even commercially. + +The licensor cannot revoke these freedoms as long as you follow the license +terms. + +Under the following terms: + +- **Attribution**---You must give appropriate credit (mentioning that your work + is derived from work that is Copyright (c) The Carpentries and, where + practical, linking to ), provide a [link to the + license][cc-by-human], and indicate if changes were made. You may do so in + any reasonable manner, but not in any way that suggests the licensor endorses + you or your use. + +- **No additional restrictions**---You may not apply legal terms or + technological measures that legally restrict others from doing anything the + license permits. With the understanding that: + +Notices: + +* You do not have to comply with the license for elements of the material in + the public domain or where your use is permitted by an applicable exception + or limitation. +* No warranties are given. The license may not give you all of the permissions + necessary for your intended use. For example, other rights such as publicity, + privacy, or moral rights may limit how you use the material. + +## Software + +Except where otherwise noted, the example programs and other software provided +by The Carpentries are made available under the [OSI][osi]-approved [MIT +license][mit-license]. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## Trademark + +"The Carpentries", "Software Carpentry", "Data Carpentry", and "Library +Carpentry" and their respective logos are registered trademarks of [Community +Initiatives][ci]. + +[cc-by-human]: https://creativecommons.org/licenses/by/4.0/ +[cc-by-legal]: https://creativecommons.org/licenses/by/4.0/legalcode +[mit-license]: https://opensource.org/licenses/mit-license.html +[ci]: https://communityin.org/ +[osi]: https://opensource.org diff --git a/accessing-remote-data.md b/accessing-remote-data.md new file mode 100644 index 0000000..34b6f1b --- /dev/null +++ b/accessing-remote-data.md @@ -0,0 +1,579 @@ +--- +title: Accessing remote data +teaching: 0 +exercises: 0 +--- + +:::::::: questions + +* How can I access data without manually saving it to my hard drive? +* How can I work with data that is behind a web API? + +:::::::: + +:::::::: objectives + +* Read remote files into `pandas` dataframes +* Investigate the inputs to and outputs from an API + +:::::::: + +## Reading files into `pandas` directly + +You asked around about the `eia923_2022.parquet` file and found out that it came from the Public Utility Data Liberation project. After a bit of online digging you find that they update their data nightly! You want to get your hands on more up-to-date data, so you want to check this out. Downloading a new file each day seems like a pain, though. You seem to remember that the documentation for `pandas.read_parquet()` mentions that the file path "could be a URL." Let's try it! + +```python +import pandas as pd + +df = pd.read_parquet("https://s3.us-west-2.amazonaws.com/pudl.catalyst.coop/nightly/core_eia923__monthly_generation_fuel.parquet") + +print(df.report_date.value_counts()) +``` + +Indeed, `read_parquet()` does handle URLs without a hiccup! You can see that there are rows for data through 2024. + +:::::::: challenge + + +Adapt your previous Excel-reading code to read the same Excel file directly from the Internet. You should be able to find the file here: ... + +:::: solution + +Much like `pd.read_excel` can read from a URL just as easily as it can read from a local file. + +```python +pd.read_excel("https://raw.githubusercontent.com/...", ...) +``` +:::: + +:::::::: + +:::::::: callout +If the person maintaining the remote data updates the data without changing the URL, you will get the new version. This can be very useful when exploring data! + +This can also be very annoying when you want your analysis to remain stable! In that case, you can try to find data that's hosted on a services like [Zenodo](https://zenodo.org/) which is designed to provide stable URLs. Many data providers also provide distinct URLs for each specific data version as well as a pointer to the latest data version. Of course, archiving the data yourself and managing the data versions can also work. +:::::::: + +## Using `requests` to download files + +It's nice to use functions in the `pd.read_*()` family with a URL, but sometimes you need to do a little bit of reshaping of the data before you can actually use `pd.read_*()`. We saw this with the XML file earlier. In those cases, you can stil tell your code to download the file instead of having to download it yourself. + +While Python has some code in the standard library to help you read data from a URL, the [`requests` library](https://requests.readthedocs.io/en/latest/user/quickstart/) is easier to use and also extremely popular, so we'll focus on using that. + +To read a URL we use the [`requests.get()` method](https://requests.readthedocs.io/en/latest/api/#requests.get), which returns a [`requests.Response` object](https://requests.readthedocs.io/en/latest/api/#requests.Response): + +```python +import requests + +response = requests.get("URL") +``` + + + +The `Response` object has many useful methods and properties, but for now we can focus on these two: +* `response.text` will provide the returned data as a *text string* +* `response.json()` will parse the returned data as if it were JSON, and provide a Python list or dictionary. + +:::::::: challenge + +The same JSON file you dealt with earlier is available online at raw.gihubusercontent.com/... + +How would you read the file contents into a dictionary called `result` without manually downloading it to your hard drive? + +a. ```python + with open("URL") as f: + result = f.read() + ``` +a. ```python + result = json.loads(requests.get("URL").text) + ``` +a. ```python + result = requests.get("URL").json() + ``` +a. ```python + with requests.get("URL") as f: + result = f.json() + ``` + +:::: solution + +The answer is + +```python +result = requests.get("URL").json() +``` + +The other answers are wrong because: +* `open()` can only read from a file on your local computer. +* `json.loads(requests.get("URL").text)` does work, but this functionality is more directly achieved with `Response.json()` +* You don't need to use `with` here because there is no cleanup required after a web request returns. +:::: + +:::::::: + +:::::::: challenge + +When might you want to use `.text` instead of `.json()`? + +:::: solution +There are many situations! Here are a few: +* if the response is in XML or HTML instead of JSON +* if the JSON is consistently malformed in some way, and you need to modify it before parsing it as JSON +* if you don't actually need to parse the JSON and instead need to store it somewhere as text +:::: + +:::::::: + + +## Introduction to APIs + +The previous postdoc left a note that he had gotten the JSON files straight from the EIA API. While you might not have dealt with a web API before, you have the tools to do so now and resolve to jump in. + +A web API consists of a number of different possible *API endpoints*, which you access via *API requests*. A useful mental model for a *web API request* is that of a *function call on someone else's computer over the Internet.* + +Imagine you have a function called `get_electric_power_operational_data` which takes a few different parameters. Maybe you call it like `get_electric_power_operational_data(data_field="generation")` and it will return a dataframe with some monthly generation data. + +The web API version of that might look like making a request to `https://api.eia.gov/v2/electricity/electric-power-operational-data/data?api_key=XYZXYZXYZ&data[]=generation`. Let's break that down. + +* `https://api.eia.gov/` specifies which other computer you're making this request to. In this case, this is the EIA API server. This also specifies that you're making this request over the Internet. +* `/v2/electricity/electric-power-operational-data/data` is the equivalent of the function name `get_electric_power_operational_data` above - it specifies what functionality you are asking for. +* `?api_key=XYZXYZXYZ&data[]=generation` is the equivalent of passing in parameters to the function: something like `(api_key="XYZXYZXYZ", data_field="generation")`. In this case we need to specify an extra `api_key` field because the other computer wants to verify that you're allowed to ask it to do things. This `?parameter_1=value_1¶meter_2=value_2` syntax is one of the main ways one passes arguments to a web API, and is called "URL parameters." The `?` precedes the first parameter, the `=` separates the parameter name from its value, and the `&` separates all parameters from each other. + +Since an API request is like a function call, learning a web API is similar to learning a new software library. For a software library, you want to quickly zero in on: + +* what are the functions that I want to call? +* what are the inputs to those functions? +* what are the outputs? + +In the case of a web API, in addition to the above you also need to figure about: + +* how do I send the input values? +* how can I get the output values back into my code? +* how do I authenticate myself to the API so it allows my request? + +## Case study: EIA API + +**TODO maybe reorganize this a little based on the framework outlined above - start with "what are the functions & the inputs/outputs", learn that "oops, you need to hit the API to figure out how to hit the API," then go to auth/input/output, and finally go back and find some good functions and parse their output.** + +Let's jump into the EIA API and see how it works! + +First of all, the full documentation is [here](https://www.eia.gov/opendata/documentation.php). We'll include relevant links and snippets as we go along. + + + +We'll start with the authentication piece. In the setup instructions, you got an API key, which is effectively the password you use to access the API. Like passwords, API keys are sensitive information. As such, we don't want to hard-code them into our code for the world to see - instead we should store them as environment variables. + +:::::::: challenge + +Store your API key in an environment variable named `EIA_API_KEY`, so that the following code prints out your API key: + + +```python +import os + +print(os.getenv("EIA_API_KEY")) +``` + +You might need to look up instructions for your specific shell (run `echo $SHELL` if you're not sure what shell you're using.) + +:::::::: + +Now that we have an API key available to us in Python, we can try to use it. Web APIs just use normal web requests, so `requests` will do the trick. + +In the documentation you can see that they expect you to pass the API key as a URL parameter, as well as a few other parameters. While the `?...` syntax works, it gets unwieldy with many parameters. `requests` provides a nice `params` dictionary we can use, which we will do below. + +Here's an example of using `requests` to get some data from the EIA API. We've glossed over exactly how to find out which URL to request, and which parameters are available, but we will touch on that later. + +```python +import os + +import requests + +EIA_API_KEY = os.getenv("EIA_API_KEY") + +response = requests.get( + f"https://api.eia.gov/v2/electricity/electric-power-operational-data/data", + params={ + "api_key": EIA_API_KEY, + "frequency": "monthly", + "data[]": "generation", + "length": 10 + } +) +print(response.json()) +``` + +Here's the output, reformatted to be more readable: + +``` +{ + "response": { + "warnings": [ + { + "warning": "incomplete return", + "description": "The API can only return 5000 rows in JSON format. Please consider constraining your request with facet, start, or end, or using offset to paginate results." + } + ], + "total": "4184767", + "dateFormat": "YYYY-MM", + "frequency": "monthly", + "data": [ + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "NGO", + "fuelTypeDescription": "natural gas & other gases", + "generation": "8.53051", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "OB2", + "fuelTypeDescription": "biomass", + "generation": ".306", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "OBW", + "fuelTypeDescription": "biomass", + "generation": ".306", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "ORW", + "fuelTypeDescription": "other renewables", + "generation": ".306", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "PEL", + "fuelTypeDescription": "petroleum liquids", + "generation": "0", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "PET", + "fuelTypeDescription": "petroleum", + "generation": "0", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "REN", + "fuelTypeDescription": "renewable", + "generation": "69.38363", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "SPV", + "fuelTypeDescription": "solar photovoltaic", + "generation": ".61609", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "SUN", + "fuelTypeDescription": "solar", + "generation": ".61609", + "generation-units": "thousand megawatthours" + }, + { + "period": "2021-03", + "location": "SAT", + "stateDescription": "South Atlantic", + "sectorid": "6", + "sectorDescription": "Industrial Non-CHP", + "fueltypeid": "WAS", + "fuelTypeDescription": "renewable waste products", + "generation": ".306", + "generation-units": "thousand megawatthours" + } + ], + "description": "Monthly and annual electric power operations by state, sector, and energy source.\n Source: Form EIA-923" + }, + "request": { + "command": "/v2/electricity/electric-power-operational-data/data/", + "params": { + "api_key": "...", + "frequency": "monthly", + "data": [ + "generation" + ], + "length": "10" + } + }, + "apiVersion": "2.1.8", + "ExcelAddInVersion": "2.1.0" +} +``` + +We see a warning, some metadata about the whole response, the actual data we're looking for, and the request that we sent. + +The warning says "The API can only return 5000 rows in JSON format. Please consider constraining your request with facet, start, or end, or using offset to paginate results." This sort of behavior is very common in web APIs - because they have to send their response to you over the Internet, they have to limit the amount of data they send at once. Much like Google search results, you will often need to go through multiple "pages" of response data. We'll deal with how to deal with that later in this lesson. + + +:::::::: challenge + +Imagine a function called `get_electric_power_operational_data()` that runs on your computer, instead of the EIA API server. One might call it like so: + +```python +get_electric_power_operational_data( + some_parameter=some_value, + ... +) +``` + +What call to that imaginary function would be equivalent to the API call we just tried out? Feel free to make up parameters for the function. + +:::: solution +```python +get_electric_power_operational_data( + api_key=EIA_API_KEY, + frequency="monthly", + data[]="generation", + length=10 +) +``` +:::: + +:::::::: + + +We can easily modify the code to request data at a yearly granularity: + +```python +import os + +import requests + +EIA_API_KEY = os.getenv("EIA_API_KEY") + +response = requests.get( + f"https://api.eia.gov/v2/electricity/electric-power-operational-data/data", + params={ + "api_key": EIA_API_KEY, + "frequency": "yearly", + "data[]": "generation", + "length": 10 + } +) +print(response.json()) +``` + +Oops! We got an error response: + +```output +{'error': "Invalid frequency 'yearly' provided. The only valid frequencies are 'monthly', 'quarterly', and 'annual'.", 'code': 400} +``` + +It tells us what the problem is - we need to use the value `annual` instead of `yearly`. + +It also tells us a "code" for the error - this expresses the general category of error and is based on the [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). Mostly you need to know that 4xx means *you* did something wrong and 5xx means *they* did something wrong. + +:::::::: challenge + +How can we figure out the valid values *without* guessing one and then reading the error message? In many APIs, you will be able to find this information in the documentation. For the EIA API, you have to make a metadata request. Read [that section of the documentation](https://www.eia.gov/opendata/documentation.php#Examiningametadatarequ). + +Then, use that to figure out what the valid data columns exist for the `electricity/electric-power-operational-data/` endpoint. + +:::: solution + +Query the `electricity/electric-power-operational-data` endpoint without `/data`. + +```python +import os + +import requests + +EIA_API_KEY = os.getenv("EIA_API_KEY") + +response = requests.get( + "https://api.eia.gov/v2/electricity/electric-power-operational-data", + params={ + "api_key": EIA_API_KEY, + } +) +``` + +```output +{ + "generation": { + "alias": "Utility Scale Electricity Net Generation" + }, + "total-consumption": { + "alias": "Consumption of Fuels for Electricity Generation and Useful Thermal Output (Physical Units)" + }, + "consumption-for-eg": { + "alias": "Consumption of Fuels for Electricity Generation (Physical Units)" + }, + "consumption-uto": { + "alias": "Consumption of Fuels for Useful Thermal Output (Physical Units)" + }, + "total-consumption-btu": { + "alias": "Consumption of Fuels for Electricity Generation and Useful Thermal Output (BTUs)" + }, + "consumption-for-eg-btu": { + "alias": "Consumption of Fuels for Electricity Generation (BTUs)" + }, + "consumption-uto-btu": { + "alias": "Consumption of Fuels for Useful Thermal Output (BTUs)" + }, + "stocks": { + "alias": "Stocks of Fuel (Physical Units)" + }, + "receipts": { + "alias": "Receipts of Fuel (Physical Units)" + }, + "receipts-btu": { + "alias": "Receipts of Fuel (BTUs)" + }, + "cost": { + "alias": "Average Cost of Fuels (per Physical Unit)" + }, + "cost-per-btu": { + "alias": "Average Cost of Fuels (per BTU)" + }, + "sulfur-content": { + "alias": "Average Sulfur Content of Consumed Fuel" + }, + "ash-content": { + "alias": "Average Ash Content of Consumed Fuel" + }, + "heat-content": { + "alias": "Average Heat Content of Consumed Fuels" + } +} +``` + +This sort of metadata request is also how we found the `/electricity/electric-power-operational-data/` endpoint in the first place, by first querying `https://api.eia.gov/v2/` and walking down the tree. If you're interested, you can try querying that. What other categories of data are available? +:::: + +:::::::: + +:::::::: challenge + +This API call will error out. Try running it - what's going wrong and how can you fix it? + +```python +response = requests.get( + url="https://api.eia.gov/v2/electricity/electric-power-operational-data/data/", + params={ + "api_key": EIA_API_KEY, + "frequency": "annual", + "data[]": "consumption", + "length": 5 + } +) +``` + +:::: solution + +If you try to make this request, the JSON response will give you information about the error: + +```json +{ + 'error': "Invalid data 'consumption' provided. The only valid data are 'generation', 'total-consumption', 'consumption-for-eg', 'consumption-uto', 'total-consumption-btu', 'consumption-for-eg-btu', 'consumption-uto-btu', 'stocks', 'receipts', 'receipts-btu', 'cost', 'cost-per-btu', 'sulfur-content', 'ash-content', and 'heat-content'.", + 'code': 400 +} +``` + +This suggests that if we change the `data[]` parameter to `total-consumption`, `consumption-for-eg`, `consumption-uto`, etc. we should get something back. + +Which one we should change it to depends on what you want. + +:::: +:::::::: + + + + + + + + + + + + + + + + + + + + + + + +## Generalizing to other APIs + +Other APIs will be different from the EIA API - both in what functionality is available, and in how you access that functionality. Let's practice reading the documentation for another useful API, this time from the EPA. + +[You can find the documentation here.](https://www.epa.gov/power-sector/cam-api-portal#/swagger/facilities-mgmt) + +:::::::: challenge + +Let's analyze this with the framework we introduced earlier. + +* what is a function that seems useful to you? +* what are their inputs/outputs? +* how do you pass parameters? +* how do you parse the response? +* how do you authenticate? + +:::::::: + +:::::::: keypoints + +* `pandas.read_*` can read tabular data from remote servers & cloud storage as if it was on your local computer +* `requests` can get data that's not in the right shape for `pandas.read_*`, so you can then get it into the Pandas shape APIs, though you'll have to do the translation from their response format into `pandas.DataFrame` yourself +* To get access to access-restricted APIs, you will usually pass in an API key, as a request *header* or as a request *parameter*. Both `requests` and `pandas.read_*` have the ability to do this. +* when encountering a new API, ask yourself these questions: + * what functions seem useful to you? + * what are their inputs/outputs? + * how do you pass parameters? + * how do you parse the response? + * how do you authenticate? + +:::::::: diff --git a/automated-testing.md b/automated-testing.md new file mode 100644 index 0000000..d918b31 --- /dev/null +++ b/automated-testing.md @@ -0,0 +1,25 @@ +--- +title: "Automated Testing" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/big-data.md b/big-data.md new file mode 100644 index 0000000..31a6ee7 --- /dev/null +++ b/big-data.md @@ -0,0 +1,25 @@ +--- +title: "Strategies for working with big data" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e8d8cf4 --- /dev/null +++ b/config.yaml @@ -0,0 +1,87 @@ +#------------------------------------------------------------ +# Values for this lesson. +#------------------------------------------------------------ + +# Which carpentry is this (swc, dc, lc, or cp)? +# swc: Software Carpentry +# dc: Data Carpentry +# lc: Library Carpentry +# cp: Carpentries (to use for instructor training for instance) +# incubator: The Carpentries Incubator +# +# This option supports custom types so lessons can be branded +# and themed with your own logo and alt-text (see `carpentry_description`) +# See https://carpentries.github.io/sandpaper-docs/editing.html#adding-a-custom-logo +carpentry: 'incubator' + +# Alt-text description of the lesson. +carpentry_description: 'Foundational data and software skills for energy data analysis.' + +# Overall title for pages. +title: 'Open Energy Data For All' + +# Date the lesson was created (YYYY-MM-DD, this is empty by default) +created: 2024-10-23 + +# Comma-separated list of keywords for the lesson +keywords: 'software, data, lesson, energy, PUDL, catalyst' + +# Life cycle stage of the lesson +# possible values: pre-alpha, alpha, beta, stable +life_cycle: 'pre-alpha' + +# License of the lesson +license: 'CC-BY 4.0' + +# Link to the source repository for this lesson +source: 'https://github.com/catalyst-cooperative/open-energy-data-for-all' + +# Default branch of your lesson +branch: 'main' + +# Who to contact if there are any issues +contact: 'hello@catalyst.coop' + +# Navigation ------------------------------------------------ +# +# Use the following menu items to specify the order of +# individual pages in each dropdown section. Leave blank to +# include all pages in the folder. + +# Order of episodes in your lesson +episodes: +# chapter 1. ingestion +- introduction.md # hmm. +- working-with-diverse-filetypes.md +- accessing-remote-data.md +- scraping-data.md +# chapter 2. visualization/reusability +- plotting.md # pick a few transforms and do them - dropping nulls, adding dtypes, stacking, unit conversion +- data-validation.md +- modularization.md +- automated-testing.md +# chapter 3. when things go wrong +- debugging.md +- big-data.md +- other-options.md # maybe cut? / roll into "here's more resources" section of big-data.md +# chapter 4. collaboration +- python-environments.md # https://carpentries-incubator.github.io/python-intermediate-development/12-virtual-environments.html +- data-environments.md +- documentation.md +- hot-takes.md + +# Information for Learners +learners: + +# Information for Instructors +instructors: + +# Learner Profiles +profiles: + +# Customisation --------------------------------------------- +# +# This space below is where custom yaml items (e.g. pinning +# sandpaper and varnish versions) should live + + diff --git a/data-environments.md b/data-environments.md new file mode 100644 index 0000000..e418922 --- /dev/null +++ b/data-environments.md @@ -0,0 +1,25 @@ +--- +title: "Managing your data environment" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/data-validation.md b/data-validation.md new file mode 100644 index 0000000..e473c42 --- /dev/null +++ b/data-validation.md @@ -0,0 +1,25 @@ +--- +title: "Data validation" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/debugging.md b/debugging.md new file mode 100644 index 0000000..cc713ea --- /dev/null +++ b/debugging.md @@ -0,0 +1,25 @@ +--- +title: "Debugging" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..77e17c9 --- /dev/null +++ b/documentation.md @@ -0,0 +1,25 @@ +--- +title: "Documentation best practices" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/fig/excelheader.png b/fig/excelheader.png new file mode 100644 index 0000000000000000000000000000000000000000..99e1497765bbfaa27fa2b1217107bcc8e744739f GIT binary patch literal 83948 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

k5}aVcLvVMu;4Z--NN{&|cXxO9;MzEJu*Tip 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 z + +[pandoc]: https://pandoc.org/MANUAL.html +[r-markdown]: https://rmarkdown.rstudio.com/ +[rstudio]: https://www.rstudio.com/ +[carpentries-workbench]: https://carpentries.github.io/sandpaper-docs/ + diff --git a/md5sum.txt b/md5sum.txt new file mode 100644 index 0000000..942f6e9 --- /dev/null +++ b/md5sum.txt @@ -0,0 +1,25 @@ +"file" "checksum" "built" "date" +"CODE_OF_CONDUCT.md" "c93c83c630db2fe2462240bf72552548" "site/built/CODE_OF_CONDUCT.md" "2022-08-05" +"LICENSE.md" "b24ebbb41b14ca25cf6b8216dda83e5f" "site/built/LICENSE.md" "2023-04-07" +"config.yaml" "e3e4437a746e9ff33e1eb98311c8eac1" "site/built/config.yaml" "2025-01-16" +"index.md" "447e37bf2e1d5254609dbb0fa9409b16" "site/built/index.md" "2024-10-24" +"links.md" "8184cf4149eafbf03ce8da8ff0778c14" "site/built/links.md" "2022-04-22" +"episodes/introduction.md" "1cf3c7fc50b2f6ddaf448fbd9e5f90b1" "site/built/introduction.md" "2024-12-19" +"episodes/working-with-diverse-filetypes.md" "232a748561562b3d35dfe074d5f48ba2" "site/built/working-with-diverse-filetypes.md" "2025-01-16" +"episodes/accessing-remote-data.md" "9018cbf267b70172474919c905bd056d" "site/built/accessing-remote-data.md" "2025-01-16" +"episodes/scraping-data.md" "ad07077eb18bcdbb46047f94082381b2" "site/built/scraping-data.md" "2025-01-16" +"episodes/plotting.md" "971edcc43024e78fdbac3a7ad8d7eb48" "site/built/plotting.md" "2024-12-13" +"episodes/data-validation.md" "313ed45011cbbc69535be499aea6af78" "site/built/data-validation.md" "2024-12-13" +"episodes/modularization.md" "eb3e0da45fe86aac554c242b4796654c" "site/built/modularization.md" "2024-12-13" +"episodes/automated-testing.md" "bd194417e7f35b53d7ae46aa26094dea" "site/built/automated-testing.md" "2024-12-13" +"episodes/debugging.md" "f55a9da71869d6490127e6308f9c8ecd" "site/built/debugging.md" "2024-12-13" +"episodes/big-data.md" "0511e7c1aa39c1af93f08aeedabc41e2" "site/built/big-data.md" "2024-12-13" +"episodes/other-options.md" "4867b7c66423155d73b9c836b87cd35b" "site/built/other-options.md" "2025-01-16" +"episodes/python-environments.md" "8db13d6805f1ea3121c72769173ac93b" "site/built/python-environments.md" "2024-12-13" +"episodes/data-environments.md" "a96791835d6f817615c1c19352a4bfb2" "site/built/data-environments.md" "2024-12-13" +"episodes/documentation.md" "fac7ff65d88edb23b07b277032e573d6" "site/built/documentation.md" "2024-12-13" +"episodes/hot-takes.md" "49a06cdbf6547f63a9f5516b10c7ac99" "site/built/hot-takes.md" "2024-12-13" +"instructors/instructor-notes.md" "cae72b6712578d74a49fea7513099f8c" "site/built/instructor-notes.md" "2023-03-16" +"learners/reference.md" "1c7cc4e229304d9806a13f69ca1b8ba4" "site/built/reference.md" "2023-03-16" +"learners/setup.md" "5456593e4a75491955ac4a252c05fbc9" "site/built/setup.md" "2024-01-26" +"profiles/learner-profiles.md" "acbbe2d0c836bc6c4d78ca6a97cdb33a" "site/built/learner-profiles.md" "2024-12-13" diff --git a/modularization.md b/modularization.md new file mode 100644 index 0000000..ef165bd --- /dev/null +++ b/modularization.md @@ -0,0 +1,25 @@ +--- +title: "Modularization" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/other-options.md b/other-options.md new file mode 100644 index 0000000..eaf4c0a --- /dev/null +++ b/other-options.md @@ -0,0 +1,27 @@ +--- +title: "Other options when your data gets big" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- what's dask +- what's spark +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/plotting.md b/plotting.md new file mode 100644 index 0000000..9aaf68d --- /dev/null +++ b/plotting.md @@ -0,0 +1,25 @@ +--- +title: "Plotting" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/python-environments.md b/python-environments.md new file mode 100644 index 0000000..0c9b8c2 --- /dev/null +++ b/python-environments.md @@ -0,0 +1,25 @@ +--- +title: "Managing Python environments" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/reference.md b/reference.md new file mode 100644 index 0000000..ba26b9f --- /dev/null +++ b/reference.md @@ -0,0 +1,8 @@ +--- +title: 'Reference' +--- + +## Glossary + +This is a placeholder file. Please add content here. + diff --git a/scraping-data.md b/scraping-data.md new file mode 100644 index 0000000..1454f8b --- /dev/null +++ b/scraping-data.md @@ -0,0 +1,30 @@ +--- +title: "Scraping Data" +teaching: 0 +exercises: 0 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- How do I get *all* of the data out of an API call when we've hit the limit for a single request? +- How do I download all the data files from a webpage? +- Can I download files from a webpage even there are no visible links? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Automatically download files from a long listing of links on a page +- Investigate webpage structure with browser dev tools +- Observe network requests and form hypotheses about how to copy them + +:::::::::::::::::::::::::::::::::::::::::::::::: + + + +::::::::::::::::::::::::::::::::::::: keypoints + +- placeholder + +:::::::::::::::::::::::::::::::::::::::::::::::: + diff --git a/setup.md b/setup.md new file mode 100644 index 0000000..4244a31 --- /dev/null +++ b/setup.md @@ -0,0 +1,54 @@ +--- +title: Setup +--- + +FIXME: Setup instructions live in this document. Please specify the tools and +the data sets the Learner needs to have installed. + +## Data Sets + + +Download the [data zip file](https://example.com/FIXME) and unzip it to your Desktop + +## Software Setup + +::::::::::::::::::::::::::::::::::::::: discussion + +### Details + +Setup for different systems can be presented in dropdown menus via a `spoiler` +tag. They will join to this discussion block, so you can give a general overview +of the software used in this lesson here and fill out the individual operating +systems (and potentially add more, e.g. online setup) in the solutions blocks. + +::::::::::::::::::::::::::::::::::::::::::::::::::: + +:::::::::::::::: spoiler + +### Windows + +Use PuTTY + +:::::::::::::::::::::::: + +:::::::::::::::: spoiler + +### MacOS + +Use Terminal.app + +:::::::::::::::::::::::: + + +:::::::::::::::: spoiler + +### Linux + +Use Terminal + +:::::::::::::::::::::::: + diff --git a/working-with-diverse-filetypes.md b/working-with-diverse-filetypes.md new file mode 100644 index 0000000..2800e29 --- /dev/null +++ b/working-with-diverse-filetypes.md @@ -0,0 +1,493 @@ +--- +title: "Handling diverse filetypes in Pandas" +teaching: 0 +exercises: 0 +--- + +Expected duration: 45 min? + +:::: questions + +- How can I read in different tabular data types to a familiar format in Python? +- What are some common errors that occur when importing data, and how can I troubleshoot them? + +:::: + +:::: objectives +- Import tabular data from Excel, XML, JSON, and Parquet formats to pandas dataframes using the `pandas` library +- Use `pandas` documentation to select and implement parameters - refine this. + +:::: + + +:::: keypoints + +- `pandas` has functionality to read in many data formats (e.g., XML, JSON, +Parquet) into the same kind of DataFrame in Python. We can take advantage of this to +transform many kinds of data with similar functions in Python. +- `pandas` accepts both relative and absolute file paths on read-in. + +:::: + +# Untangling a data pile +While poking around in your lab's computer, you find the folder that the postdoc was +using to store data inputs to his model. Inside the `data` folder, however, is a bit of +a mess! Every file in the folder has the same name ("eia923_2022") but a different file +extension. To make sense of this undocumented pile of files, we'll need to read in each +file and compare them. + +# EIA 923 data +The Energy Information Administration (EIA)'s [Form 923](https://www.eia.gov/electricity/data/eia923) is known as the Power Plant Operations Report. The data include electric power generation, energy source consumption, end of reporting period fossil fuel stocks, as well as the quality and cost of fossil fuel receipts at the power plant and prime mover level (with a subset of +10MW steam-electric plants reporting at the boiler and generator level). Information is available for non-utility plants starting in 1970 and utility plants beginning in 1999. The Form EIA-923 has evolved over the years, beginning as an environmental add-on in 2007 and ultimately eclipsing the information previously recorded in EIA-906, EIA-920, FERC 423, and EIA-423 by 2008. + + TODO: Why is it a good choice for answering the question? + +# Reading Excel files with Pandas + +One of the most popular libraries used to work with tabular data in Python is called the +[Python Data Analysis Library](https://pandas.pydata.org/) (or simply, Pandas). Pandas +has functions to handle reading in a diversity of file types, from CSVs and Excel spreadsheets to more complex data formats such as XML and Parquet. Each read function offers a variety of parameters designed to handle common complexities specific to the file type on import. For a refresher (TODO: Is this an ok wording?) on Pandas, Pandas DataFrames and reading in files, see the [Starting with Data](https://datacarpentry.github.io/python-ecology-lesson/instructor/02-starting-with-data.html) lesson. + +Of all the files in the `data` folder, you decide to start with the Excel spreadsheet. +To read in an Excel spreadsheet using `pandas`, you will use the `read_excel()` function: + +```python +import pandas as pd +pd.read_excel('data/eia923_2022.xlsx') +``` + +Unfortunately, something doesn't look quite right! When opening the file in a +spreadsheet software, you see that the first few rows look like this: + +![The first few rows of the eia923_2022.xlsx file](fig/excelheader.png){alt="Snapshot of +the Excel file showing the first 6 rows contain metadata, blank spaces and column +names."} + +To read the spreadsheet in correctly, we want to ignore these first five rows. Luckily, +`read_excel()` offers built-in functionality to handle various Excel formatting +challenges. To identify which parameter we need to use to skip these rows when reading +in the file, we can use the `help()` function to pull up the function documentation: + +```python +help(pd.read_excel) +``` + +For each parameter, the documentation provides the name of the parameter, the format for the parameter input (e.g., list, string, int), the default value if no value is provided, and an explanation of what the parameter does. + +For example, the `nrows` parameter provides the following documentation: + +```output +nrows : int, default None + Number of rows to parse. +``` + +So, if we only want to parse the first 100 rows of the data, we can call: + +```python +pd.read_excel('data/eia923_2022.xlsx', nrows=100) +``` + +:::callout +TODO: Absolute and relative file paths should live here? Or elsewhere? +::: + +:::::::: challenge + +## Challenge 1: handling Excel formatting on read-in + +Looking at the documentation for `pd.read_excel()`, identify the parameter needed to skip the first few rows of the spreadsheet. Then, using `pd.read_excel()`, read in the "Page 1 Generation and Fuel Data" sheet using this parameter to skip any rows that don't contain the column headers. + +:::: solution + +```python +import pandas as pd + +excel_923 = pd.read_excel('data/eia923_2022.xlsx', sheet_name="Page 1 Generation and Fuel Data", skiprows=5) + +# sheet_name can also take the number of the sheet +excel_923 = pd.read_excel('data/eia923_2022.xlsx', sheet_name=0, skiprows=5) +``` + +:::: + +:::::::: + +Each row contains monthly generation data for each plant's prime mover. While a subset of plants fill out Form 923 at the boiler and generator, a large proportion of plants only report at this more aggregated level. For more on the nuances of the Form 923 data, see PUDL's [data source page](https://catalystcoop-pudl.readthedocs.io/en/latest/data_sources/eia923.html). + +# Reading JSON files with Pandas + +JavaScript Object Notation (JSON) is a lightweight file format based on name-value pairs, similar to Python dictionaries. JSON often used to send data to and from web applications, and is one of the most common formats provided when you're accessing data from an Application Programming Interface (API). JSON data can be found saved as either `.json` or `.txt` files. + +If we look at a single record from `eia923_2022_sample.json`, we can see that each name-value pair corresponds to one row and one column in a tabular dataset. + +```output +{"period":"2022-12","plantCode":"6761", + "plantName":"Rawhide","fuel2002":"ALL","fuelTypeDescription":"Total","state":"CO","stateDescription":"Colorado","primeMover":"ALL","generation":"188961","gross-generation":"203283","generation-units":"megawatthours","gross-generation-units":"megawatthours"} +``` + +Like `read_excel`, we can use the the Pandas `read_json()` method to read in a JSON file. + +```python +pd.read_json('data/eia923_2022_sample.json') +``` + +This yields the following DataFrame, with each JSON record transformed into a row: + +```output + period plantCode plantName fuel2002 fuelTypeDescription state stateDescription primeMover generation gross-generation generation-units gross-generation-units +0 2022-12 6761 Rawhide ALL Total CO Colorado ALL 188961.00 203283.00 megawatthours megawatthours +1 2022-12 54142 Hillcrest Pump Station WAT Hydroelectric Conventional CO Colorado HY 342.43 358.27 megawatthours megawatthours +2 2022-12 54142 Hillcrest Pump Station WAT Hydroelectric Conventional CO Colorado ALL 342.43 358.27 megawatthours megawatthours +3 2022-12 54142 Hillcrest Pump Station ALL Total CO Colorado ALL 342.43 358.27 megawatthours megawatthours +4 2022-12 64359 Pivot Solar 12 LLC(CSG) SUN Solar CO Colorado PV 92.45 92.45 megawatthours megawatthours +``` + +However, there are only four records here! From the Excel spreadsheet you read in earlier, you know this is only a small sample of the data. Let's try to read in the second JSON file the postdoc left behind: + +```python +pd.read_json('data/eia923_2022.json') +``` + +```output +| response | request | apiVersion | ExcelAddInVersion | | +|:-----------:|:-------------------------------------------------:|:-------------------------------------------------:|-------------------|-------| +| warnings | [{'warning': 'incomplete return', 'description... | NaN | 2.1.8 | 2.1.0 | +| total | 10426 | NaN | 2.1.8 | 2.1.0 | +| dateFormat | YYYY-MM | NaN | 2.1.8 | 2.1.0 | +| frequency | monthly | NaN | 2.1.8 | 2.1.0 | +| data | [{'period': '2022-12', 'plantCode': '6761', 'p... | NaN | 2.1.8 | 2.1.0 | +| description | Annual and monthly electric power operations f... | NaN | 2.1.8 | 2.1.0 | +| command | NaN | /v2/electricity/facility-fuel/data/ | 2.1.8 | 2.1.0 | +| params | NaN | {'frequency': 'monthly', 'data': ['generation'... | 2.1.8 | 2.1.0 | +``` + +We can see the data we want as a single record in the fifth row, but the format returned isn't usable for analysis. That's because this JSON is *nested*! + +A nested JSON contains multiple levels of data: + +```output +{'response': + {'data': [ + {'period':'2022-12', + 'plantCode': '6761'}, + {'period':'2022-12', + 'plantCode': '54152'} + ] + }}} +``` + +Here, the `response` name contains another name-value pair called `data`, and `data` +contains a list with two records, each of which has two name-value pairs (`period` and `plantCode`). Pandas `read_*()` methods transform data into a tabular format. To successfully extract tabular generation data from our nested JSON, we need to identify which part of the nested JSON contains the tabular data we're looking for. + +To better visualize our nested JSON file, let's read it into Python without changing its format. To do this, we use the `json` package, and the `load` method. + +While Pandas handles opening a file in the `read_*()` methods, `json.load()` does not - so, we first need to open the file in Python. To do so, we use the `open()` function to read the `eia923_2022.json` file. + +:::callout +When we `open()` a file in Python, we should always close it after we've extracted the data we need. Closing a file frees up system resources and ensures that we aren't accidentally modifying our original file. + +To automatically handle file opening and closing, we use a *context manager*. Using the word `with`, we put all the code we want to run on the opened file into an indented block. +::: + +```python +import json +with open('data/eia923_2022.json') as file: + eia923_json = json.load(file) + +eia923_json +``` +The first part of the response looks like this: + +```output +{'response': {'warnings': [{'warning': 'incomplete return', + 'description': 'The API can only return 5000 rows in JSON format. Please consider constraining your request with facet, start, or end, or using offset to paginate results.'}], + 'total': '10426', + 'dateFormat': 'YYYY-MM', + 'frequency': 'monthly', + 'data': [{'period': '2022-12', + 'plantCode': '6761', + 'plantName': 'Rawhide', + 'fuel2002': 'ALL', + 'fuelTypeDescription': 'Total', + 'state': 'CO', + 'stateDescription': 'Colorado', + 'primeMover': 'ALL', + 'generation': '188961', + 'gross-generation': '203283', + 'generation-units': 'megawatthours', + 'gross-generation-units': 'megawatthours'}, + ..... +``` +Now we can treat `eia923_json` like any other Python dictionary. To see the value of any particular key (*TODO: stick to name-value or switch to key-value??*), we can call it in square brackets by name: + +```python +eia923_json['response'] +``` + +This returns yet another dictionary with a list of keys. To look more closely at the `warnings` the file contains, we can add another square bracket: + +```python +eia923_json['response']['warnings'] +``` + +```output +[{"warning":"incomplete return","description":"The API can only return 5000 rows in JSON format. Please consider constraining your request with facet, start, or end, or using offset to paginate results."}, {"warning":"another warning", "description":"Hey! Watch out!"}] +``` + *TODO: Should I dupe a second warning??* + +This format looks identical to the `eia923_2022_sample.json` we worked with earlier: a list of dictionaries with consistent name-value pairs. It's time to turn these warnings into a table! + +## Using `json.normalize()` +Luckily for us, Pandas has a second function transforming nested or semi-structured JSON files into Pandas DataFrames: `json_normalize()`. + +Unlike `read_json()`, `json_normalize()` expects that the JSON object has already been read into Python using `json.load()`. Using the `record_path` +parameter, we can specify the path to follow to get to our tabular data - in this case, first `response` and then `warnings`: + +```python +pd.read_json(eia923_json, record_path = ['response','warnings']) +``` +The function returns a DataFrame that looks like this: + +```output +| warning | description | +|------------------:|--------------------------------------------------:| +| incomplete return | The API can only return 5000 rows in JSON form... | +| another warning | Hey! Watch out! | +``` + +The first row of this table is letting us know that when we queried and saved this data from the API, we only got the first 5000 rows of data - yikes! We'll tackle this problem in a later episode, but for now let's investigate the data that we do have saved locally. + +:::::::: challenge + +## Challenge 2: handling nested JSONs + +Fill in the blanks in the code below to read in the `data` from the `eia923_2022.json` file into a Pandas DataFrame. + +```python +import pandas as pd +import json + +import json +with open('data/eia923_2022.json') as file: + eia923_json = ... + +eia923_json_df = pd.json_normalize(eia923_json, record_path = [...]) + +``` + +:::: solution + +```python +import pandas as pd +import json + +import json +with open('data/eia923_2022.json') as file: + eia923_json = json.load(file) + +eia923_json_df = pd.json_normalize(eia923_json, record_path = ['response', 'data']) + +``` + +:::: + +:::::::: + +:::callout +JSONs can include many levels of nesting, including different levels of nesting for similar records or other formatting that doesn't obey the principles of tabular structure (where each row represents a single record, and each column represents a single variable). `pd.json_normalize()` provides a set of parameters that can used to wrangle more deeply nested JSON data. Call `help(pd.json_normalize)` and look at the provided examples to get a better sense of its capabilities. +::: + +# Deciphering XML + +eXtensible Markup Language (XML) is a plain text file that uses tags to describe the +structure and content of the data they contain. Like other markup languages (HTML, LaTeX), +XML wraps around data, providing information about the structure, format, and relationships +between components. Each tag provides metadata about what the piece of data it contains +represents - for instance `` will contain a row of data, while ` 243 ` will +means that the plant code is 243. + +For example, the following might be a way to represent a note from +Saul R. Panel to Dr. Watts apologizing for leaving the project in an incomplete state: + +```xml + + Saul R. Panel + Dr. Watts + Note about project + Sorry for leaving the project in an incomplete state! + +``` + +In JSON, the equivalent information could be formatted as: +```output +{"note": + {"to": "Saul R. Panel", + "from": "Dr. Watts", + "heading": "Note about project", + "body": "Sorry for leaving the project in an incomplete state!" + } +} +``` + +Each tag in XML is similar to a key in a JSON file: +* both provide metadata about what the corresponding value _is_ (e.g., a note, net generation in watts) +* both provide information about nested relationships (e.g., the note contains a heading and a body) + +While XML is harder and slower to read than JSON, it also has more capabilities. You might +be likely to see an XML file if the data you're looking at: +* is old! XML was invented in 1998 and is still widely in use in older data distribution +methods. +* has deeply nested hierarchies of relationships, like FERC's accounting data. +* is large and complex! For instance, XML can be used to share images, charts and graphs +in addition to text data, while JSON can only handle text. +* is distributed through an RSS feed. For instance, FERC publishes filings on a rolling +basis using an RSS feed and the XML data format. + +::: challenge +## Challenge #: From XML to Pandas + +Look at the following XML code. + +```output + + + 2022-12 + 59656 + Comanche Solar + + + 2023-01 + 59657 + Comanche + +``` + +Which of the following Pandas DataFrames would best represent the data in this XML file? + +A. +```output +| data | row | +|:---------:|:--------------:| +| period | 2022-12 | +| plantCode | 59656 | +| plantName | Comanche Solar | +| period | 2023-01 | +| plantCode | 59657 | +| plantName | Comanche | +``` + +B. +```output +| row | data | +|:---------:|:--------------:| +| period | 2022-12 | +| plantCode | 59656 | +| plantName | Comanche Solar | +| period | 2023-01 | +| plantCode | 59657 | +| plantName | Comanche | +``` + +C. +``` +| period | plantCode | plantName | +|:-------:|-----------|----------------| +| 2022-12 | 59656 | Comanche Solar | +| 2023-01 | 59657 | Comanche | +``` + +D. +``` +| plantName | Comanche Solar | Comanche | +|:---------:|----------------|----------| +| period | 2022-12 | 2023-01 | +| plantCode | 59656 | 59657 | +``` + +:::solution +The solution is C: +``` +| period | plantCode | plantName | +|:-------:|-----------|----------------| +| 2022-12 | 59656 | Comanche Solar | +| 2023-01 | 59657 | Comanche | +``` + +The `` is split into two seperate chunks of data seperated by `` tags, which +tells us that everything between these tags corresponds to a single row of data. Then, +we know that the ``, `` and `` tags are telling us what +variable the values correspond to - or in other words, what the column name is that +corresponds to each tag. +::: + +::: + +# `pd.read_xml() +Like with our other data types, we can use `pd.read_xml()` to parse XML files into Pandas DataFrames. `pd.read_xml()` is designed to ingest tabular data nested in XML files, +not to coerce highly nested data into a table format. To use this method, we'll need to +identify where in our XML file the data is structured into a table-like format and can +be easily extracted to a DataFrame. For more on `pd.read_xml()`, see the [Pandas documentation](https://pandas.pydata.org/docs/reference/api/pandas.read_xml.html#pandas.read_xml#notes). + +Let's try to explore the XML file that the postdoc left behind: +```python +pd.read_xml('data/eia923_2022.xml') +``` + +Each tag has been assigned as a column name, and the value inside has been added as a row. To drill down to the `` we are actually interested in, +we can use the `xpath` parameter, which lets you specify where in the XML file to look for a table. + +The `xpath` query we're looking for is formatted as follows: +* // are used at the beginning to note that we want to select all items with the tags specified +* Then, like specifying which directory we want to access in a terminal, slashes are used to specify the path to the desired tag. + +So to get all the ``s of ``, we call: +```python +pd.read_xml('data/eia923_2022.xml', xpath = "//response/data/row") +``` + +Note that we have one extra column called row in our dataframe, which contains yet another warning. Let's inspect it: + +```python +xml_df = pd.read_xml('data/eia923_2022.xml', xpath = "//response/data/row") +xml_df.loc[0, 'row'] # Grab the first row of the "row" column +``` + +```output +'warning: XML output returns a maximum of 300 rows; use offset= and length= parameters or output to JSON for entire dataset.' +``` + +When we start to work with this dataset, we'll want to make sure that we heed this warning! + +# `pd.read_parquet() + +There's one more file left in the `data` folder the postdoc left behind - a Parquet file! +You can think of Parquet files as spreadsheet storage optimized for computers. + +Parquet files: +* store data column by column, rather than row by row +* are designed to efficiently process and store large volumes of data, making it about +50x faster than using `pd.read_csv()` on comparable file sizes. +* compresses data efficiently, reducing file size +* are saved with data organized into chunks (e.g., one chunk per month), making it possible +to quickly load data from some part of the dataset without loading everything into memory. +* are supported by many existing tools, including `Pandas`. + +We can read a Parquet file to a Pandas DataFrame using `pd.read_parquet()`, almost identical to how we read in a CSV: + +```py +eia923_parquet = pd.read_parquet('data/eia923_2022.parquet') +``` + +:::::::: challenge + +Pick two datasets we've just read in, and compare them. How are they similar, and how are they different? Share your reflections with a peer. + +:::: hint + +* Inspect a column in a DataFrame `df` by using `df[column_name]`. +* To quickly see what values are contained in a column, you can use `df[column_name].unique()` to get a list of unique values in the column. +* Try using `df.iloc[0]` to get the values from the first row of the data. +* `df.head(n)` returns the first n rows of the data, and `df.tail(n)` returns the last n rows. +* to add - isin()?? info() +:::: + +:::::::: \ No newline at end of file