{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sqlite3\n", "import sys\n", "\n", "sys.path.append(\"..\")\n", "\n", "import nivapy3 as nivapy\n", "import numpy as np\n", "import pandas as pd\n", "import utils" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Tiltaksovervakingen: opsjon for kvalitetskontroll av analysedata\n", "# Eurofins 2022 Q3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Notebook 1: Initial exploration and data cleaning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Initial data check\n", "\n", "This notebook explores the 2022 Q3 data from **Eurofins** sent by Marianne Isebakke on 10.11.2022 at 11.56. The code performs initial data exploration and cleaning, with the aim of creating a tidy dataset in SQLite that can be used for further analysis.\n", "\n", "**Note:** This file uses an updated export from Vannmiljø reflecting corrections made to two station codes in August 2021 - see e-mail from Kjetil received 27.05.2021 at 17:15 for details." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Choose dataset to process\n", "lab = \"Eurofins\"\n", "year = 2022\n", "qtr = 3\n", "version = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Create SQLite database to store results\n", "\n", "Using a database will provide basic checks on data integrity and consistency. For this project, three tables will be sufficient:\n", "\n", " * Station locations and metadata\n", " * Parameters and units used by VestfoldLAB, Eurofins and Vannmiljø, and conversion factors between these\n", " * Water chemistry data\n", " \n", "The code below creates a basic database structure, which will be populated later." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create database\n", "fold_path = f\"../../output/{lab.lower()}_{year}_q{qtr}_v{version}\"\n", "if not os.path.exists(fold_path):\n", " os.makedirs(fold_path)\n", "\n", "db_path = os.path.join(fold_path, \"kalk_data.db\")\n", "if os.path.exists(db_path):\n", " os.remove(db_path)\n", "eng = sqlite3.connect(db_path, detect_types=sqlite3.PARSE_DECLTYPES)\n", "\n", "# Turn off journal mode for performance\n", "eng.execute(\"PRAGMA synchronous = OFF\")\n", "eng.execute(\"PRAGMA journal_mode = OFF\")\n", "eng.execute(\"PRAGMA foreign_keys = ON\")\n", "\n", "# Create stations table\n", "sql = (\n", " \"CREATE TABLE stations \"\n", " \"( \"\n", " \" fylke text NOT NULL, \"\n", " \" vassdrag text NOT NULL, \"\n", " \" station_name text NOT NULL, \"\n", " \" station_number text, \"\n", " \" vannmiljo_code text NOT NULL, \"\n", " \" vannmiljo_name text, \"\n", " \" utm_east real NOT NULL, \"\n", " \" utm_north real NOT NULL, \"\n", " \" utm_zone integer NOT NULL, \"\n", " \" lon real NOT NULL, \"\n", " \" lat real NOT NULL, \"\n", " \" liming_status text NOT NULL, \"\n", " \" comment text, \"\n", " \" PRIMARY KEY (vannmiljo_code) \"\n", " \")\"\n", ")\n", "eng.execute(sql)\n", "\n", "# Create parameters table\n", "sql = (\n", " \"CREATE TABLE parameters_units \"\n", " \"( \"\n", " \" vannmiljo_name text NOT NULL UNIQUE, \"\n", " \" vannmiljo_id text NOT NULL UNIQUE, \"\n", " \" vannmiljo_unit text NOT NULL, \"\n", " \" vestfoldlab_name text NOT NULL UNIQUE, \"\n", " \" vestfoldlab_unit text NOT NULL, \"\n", " \" vestfoldlab_to_vm_conv_fac real NOT NULL, \"\n", " \" eurofins_name text NOT NULL UNIQUE, \"\n", " \" eurofins_unit text NOT NULL, \"\n", " \" eurofins_to_vm_conv_fac real NOT NULL, \"\n", " \" min real NOT NULL, \"\n", " \" max real NOT NULL, \"\n", " \" PRIMARY KEY (vannmiljo_id) \"\n", " \")\"\n", ")\n", "eng.execute(sql)\n", "\n", "# Create chemistry table\n", "sql = (\n", " \"CREATE TABLE water_chemistry \"\n", " \"( \"\n", " \" vannmiljo_code text NOT NULL, \"\n", " \" sample_date datetime NOT NULL, \"\n", " \" lab text NOT NULL, \"\n", " \" period text NOT NULL, \"\n", " \" depth1 real, \"\n", " \" depth2 real, \"\n", " \" parameter text NOT NULL, \"\n", " \" flag text, \"\n", " \" value real NOT NULL, \"\n", " \" unit text NOT NULL, \"\n", " \" PRIMARY KEY (vannmiljo_code, sample_date, depth1, depth2, parameter), \"\n", " \" CONSTRAINT vannmiljo_code_fkey FOREIGN KEY (vannmiljo_code) \"\n", " \" REFERENCES stations (vannmiljo_code) \"\n", " \" ON UPDATE NO ACTION ON DELETE NO ACTION, \"\n", " \" CONSTRAINT parameter_fkey FOREIGN KEY (parameter) \"\n", " \" REFERENCES parameters_units (vannmiljo_id) \"\n", " \" ON UPDATE NO ACTION ON DELETE NO ACTION \"\n", " \")\"\n", ")\n", "eng.execute(sql)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Explore station data\n", "\n", "Station details are stored in `../../data/active_stations_2020.xlsx`, which is a tidied version of Øyvind's original file here:\n", "\n", " K:\\Prosjekter\\langtransporterte forurensninger\\Kalk Tiltaksovervåking\\12 KS vannkjemi\\Vannlokaliteter koordinater_kun aktive stasj 2020.xlsx\n", " \n", "Note that corrections (e.g. adjusted station co-ordinates) have been made to the tidied file, but not the original on `K:`. **The version in this repository should therefore been used as the \"master\" copy**." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The following stations are missing spatial co-ordinates:\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
fylkevassdragstation_namestation_numbervannmiljo_codevannmiljo_nameutm_eastutm_northutm_zoneliming_statuscommentlatlon
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [fylke, vassdrag, station_name, station_number, vannmiljo_code, vannmiljo_name, utm_east, utm_north, utm_zone, liming_status, comment, lat, lon]\n", "Index: []" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Read station data\n", "stn_df = pd.read_excel(r\"../../data/active_stations_2020.xlsx\", sheet_name=\"data\")\n", "stn_df = nivapy.spatial.utm_to_wgs84_dd(stn_df)\n", "\n", "print(\"The following stations are missing spatial co-ordinates:\")\n", "stn_df.query(\"lat != lat\")" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The following stations do not have a code in Vannmiljø:\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
fylkevassdragstation_namestation_numbervannmiljo_codevannmiljo_nameutm_eastutm_northutm_zoneliming_statuscommentlatlon
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [fylke, vassdrag, station_name, station_number, vannmiljo_code, vannmiljo_name, utm_east, utm_north, utm_zone, liming_status, comment, lat, lon]\n", "Index: []" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"The following stations do not have a code in Vannmiljø:\")\n", "stn_df.query(\"vannmiljo_code != vannmiljo_code\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Map\n", "stn_map = nivapy.spatial.quickmap(\n", " stn_df.dropna(subset=[\"lat\"]),\n", " lat_col=\"lat\",\n", " lon_col=\"lon\",\n", " popup=\"station_name\",\n", " cluster=True,\n", " kartverket=True,\n", " aerial_imagery=True,\n", ")\n", "\n", "stn_map.save(\"../../pages/stn_map.html\")\n", "\n", "stn_map" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "222" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Add to database\n", "stn_df.dropna(subset=[\"vannmiljo_code\", \"lat\"], inplace=True)\n", "stn_df.to_sql(name=\"stations\", con=eng, if_exists=\"append\", index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Parameters and units of interest\n", "\n", "The file `../../data/parameter_unit_mapping.xlsx` provides a lookup between parameter names & units used by the labs and those in Vannmiljø. It also contains plausible ranges (using Vannmiljø units) for each parameter. These ranges have been chosen by using the values already in Vannmiljø as a reference. However, it looks as though some of the data in Vannmiljø might also be spurious, so it would be good to refine these ranges based on domain knowledge, if possible.\n", "\n", "**Note:** Concentrations reported as *exactly* zero are likely to be errors, because most (all?) lab methods should report an LOQ instead." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vannmiljo_namevannmiljo_idvannmiljo_unitvestfoldlab_namevestfoldlab_unitvestfoldlab_to_vm_conv_faceurofins_nameeurofins_uniteurofins_to_vm_conv_facminmax
0TemperaturTEMP°CTemp°C1.000000Temp°C1.000000-1030
1pHPH<ubenevnt>pHenh1.000000pHenh1.000000110
2KonduktivitetKONDmS/mKondmS/m1.000000Kondms/m1.0000000100
3Total alkalitetALKmmol/lAlkmmol/l1.000000Alkmmol/l1.00000002
4TotalfosforP-TOTµg/l PTot-Pµg/l1.000000Tot-Pµg/l1.0000000500
5TotalnitrogenN-TOTµg/l NTot-Nµg/l1.000000Tot-Nµg/l1.00000004000
6NitratN-NO3µg/l NNO3µg/l1.000000NO3µg/l1.00000002000
7Totalt organisk karbon (TOC)TOCmg/l CTOCmg/l1.000000TOCmg/l1.0000000100
8Reaktivt aluminiumRALµg/l AlRAlµg/l1.000000RAlµg/l1.0000000500
9Ikke-labilt aluminiumILALµg/l AlILAlµg/l1.000000ILAlµg/l1.0000000500
10Labilt aluminiumLALµg/l AlLAlµg/l1.000000LAlµg/l1.0000000500
11KloridCLmg/lClmg/l1.000000Clmg/l1.0000000100
12SulfatSO4mg/lSO4mg/l1.000000SO4mg/l1.000000020
13KalsiumCAmg/lCamg/l1.000000Camg/l1.0000000500
14KaliumKmg/lKmg/l1.000000Kmg/l1.000000010
15MagnesiumMGmg/lMgmg/l1.000000Mgmg/l1.0000000100
16NatriumNAmg/lNamg/l1.000000Namg/l1.000000050
17Totalt silikatSIO2µg/l SiSIO2mg/l467.543276SIO2µg/l0.46754307000
18Syrenøytraliserende kapasitet (ANC)ANCµekv/lANCµekv/l1.000000ANCµekv/l1.000000-10006000
\n", "
" ], "text/plain": [ " vannmiljo_name vannmiljo_id vannmiljo_unit \\\n", "0 Temperatur TEMP °C \n", "1 pH PH \n", "2 Konduktivitet KOND mS/m \n", "3 Total alkalitet ALK mmol/l \n", "4 Totalfosfor P-TOT µg/l P \n", "5 Totalnitrogen N-TOT µg/l N \n", "6 Nitrat N-NO3 µg/l N \n", "7 Totalt organisk karbon (TOC) TOC mg/l C \n", "8 Reaktivt aluminium RAL µg/l Al \n", "9 Ikke-labilt aluminium ILAL µg/l Al \n", "10 Labilt aluminium LAL µg/l Al \n", "11 Klorid CL mg/l \n", "12 Sulfat SO4 mg/l \n", "13 Kalsium CA mg/l \n", "14 Kalium K mg/l \n", "15 Magnesium MG mg/l \n", "16 Natrium NA mg/l \n", "17 Totalt silikat SIO2 µg/l Si \n", "18 Syrenøytraliserende kapasitet (ANC) ANC µekv/l \n", "\n", " vestfoldlab_name vestfoldlab_unit vestfoldlab_to_vm_conv_fac \\\n", "0 Temp °C 1.000000 \n", "1 pH enh 1.000000 \n", "2 Kond mS/m 1.000000 \n", "3 Alk mmol/l 1.000000 \n", "4 Tot-P µg/l 1.000000 \n", "5 Tot-N µg/l 1.000000 \n", "6 NO3 µg/l 1.000000 \n", "7 TOC mg/l 1.000000 \n", "8 RAl µg/l 1.000000 \n", "9 ILAl µg/l 1.000000 \n", "10 LAl µg/l 1.000000 \n", "11 Cl mg/l 1.000000 \n", "12 SO4 mg/l 1.000000 \n", "13 Ca mg/l 1.000000 \n", "14 K mg/l 1.000000 \n", "15 Mg mg/l 1.000000 \n", "16 Na mg/l 1.000000 \n", "17 SIO2 mg/l 467.543276 \n", "18 ANC µekv/l 1.000000 \n", "\n", " eurofins_name eurofins_unit eurofins_to_vm_conv_fac min max \n", "0 Temp °C 1.000000 -10 30 \n", "1 pH enh 1.000000 1 10 \n", "2 Kond ms/m 1.000000 0 100 \n", "3 Alk mmol/l 1.000000 0 2 \n", "4 Tot-P µg/l 1.000000 0 500 \n", "5 Tot-N µg/l 1.000000 0 4000 \n", "6 NO3 µg/l 1.000000 0 2000 \n", "7 TOC mg/l 1.000000 0 100 \n", "8 RAl µg/l 1.000000 0 500 \n", "9 ILAl µg/l 1.000000 0 500 \n", "10 LAl µg/l 1.000000 0 500 \n", "11 Cl mg/l 1.000000 0 100 \n", "12 SO4 mg/l 1.000000 0 20 \n", "13 Ca mg/l 1.000000 0 500 \n", "14 K mg/l 1.000000 0 10 \n", "15 Mg mg/l 1.000000 0 100 \n", "16 Na mg/l 1.000000 0 50 \n", "17 SIO2 µg/l 0.467543 0 7000 \n", "18 ANC µekv/l 1.000000 -1000 6000 " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Read parameter mappings\n", "par_df = utils.get_par_unit_mappings()\n", "\n", "# Add to database\n", "par_df.to_sql(name=\"parameters_units\", con=eng, if_exists=\"append\", index=False)\n", "\n", "par_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Historic data from Vannmiljø\n", "\n", "The Vannmiljø dataset is large and reading from Excel is slow; the code below takes a couple of minutes to run.\n", "\n", "Note from the output below that **there are more than 1600 \"duplicated\" samples in the Vannmiljø dataset** i.e. where the station code, sample date, sample depth, lab and parameter name are all the same, but a different value is reported. It would be helpful to know why these duplicates were collected e.g. are these reanalysis values, where only one of the duplicates should be used, or are they genuine (in which case should they be averaged or kept separate?). **For the moment, I will ignore these values**." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The number of unique stations with data is: 221.\n", "\n", "\n", "There are 2578 duplicated records (same station_code-date-depth-parameter, but different value).\n", "These will be dropped.\n", "\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vannmiljo_codesample_datelabdepth1depth2par_unitflagvalueperiod
532019-444982012-01-02NIVA (historic)0.00.0K_mg/l=0.19historic
533019-444982012-01-02NIVA (historic)0.00.0KOND_mS/m=1.71historic
534019-444982012-02-15NIVA (historic)0.00.0CL_mg/l=1.15historic
535019-444982012-02-15NIVA (historic)0.00.0KOND_mS/m=1.40historic
536019-444982012-03-05NIVA (historic)0.00.0CL_mg/l=1.83historic
\n", "
" ], "text/plain": [ " vannmiljo_code sample_date lab depth1 depth2 par_unit \\\n", "532 019-44498 2012-01-02 NIVA (historic) 0.0 0.0 K_mg/l \n", "533 019-44498 2012-01-02 NIVA (historic) 0.0 0.0 KOND_mS/m \n", "534 019-44498 2012-02-15 NIVA (historic) 0.0 0.0 CL_mg/l \n", "535 019-44498 2012-02-15 NIVA (historic) 0.0 0.0 KOND_mS/m \n", "536 019-44498 2012-03-05 NIVA (historic) 0.0 0.0 CL_mg/l \n", "\n", " flag value period \n", "532 = 0.19 historic \n", "533 = 1.71 historic \n", "534 = 1.15 historic \n", "535 = 1.40 historic \n", "536 = 1.83 historic " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Read historic data from Vannmiljø\n", "his_df = utils.read_historic_data(\n", " r\"../../data/vannmiljo_export_2012-20_2022-08-19.xlsx\",\n", " st_yr=2012,\n", " end_yr=2020,\n", ")\n", "\n", "# Tidy lab names for clarity\n", "his_df[\"lab\"].replace(\n", " {\n", " \"NIVA\": \"NIVA (historic)\",\n", " \"VestfoldLAB AS\": \"VestfoldLAB (historic)\",\n", " \"Eurofins Environment Testing Norway AS (Moss)\": \"Eurofins (historic)\",\n", " },\n", " inplace=True,\n", ")\n", "\n", "# Add label for data period\n", "his_df[\"period\"] = \"historic\"\n", "\n", "# Print summary\n", "n_stns = len(his_df[\"vannmiljo_code\"].unique())\n", "print(f\"The number of unique stations with data is: {n_stns}.\\n\")\n", "\n", "# Handle duplicates\n", "his_dup_csv = r\"../../output/vannmiljo_historic/vannmiljo_duplicates.csv\"\n", "his_df = utils.handle_duplicates(his_df, his_dup_csv, action=\"drop\")\n", "\n", "his_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. New data from labs\n", "\n", "The code below reads the Excel template provided by Eurofins and reformats it to the same structure (parameter names, units etc.) as the data in Vannmiljø." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Checking stations:\n", "\n", "The following location IDs have inconsistent names within this template:\n", "\n", "The following location names have multiple IDs within this template:\n", "\n", "Checking sample dates:\n", " Done.\n", "\n", "Checking for non-numeric data:\n", " Done.\n", "\n", "Checking for values less than zero:\n", " Done.\n", "\n", "Checking for consistent LOD values:\n", " Done.\n", "\n", "Checking NO3 and TOTN:\n", " Done.\n", "\n", "Checking Al fractions:\n", " Done.\n", "\n", "There are 86 duplicated records (same station_code-date-depth-parameter, but different value).\n", "These will be dropped.\n", "\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vannmiljo_codesample_datelabdepth1depth2par_unitflagvalueperiod
0027-792782022-07-05Eurofins0.00.0TEMP_°Cnan17.0new
1027-792782022-08-02Eurofins0.00.0TEMP_°Cnan15.0new
2027-792782022-09-06Eurofins0.00.0TEMP_°Cnan15.0new
3019-587932022-07-05Eurofins0.00.0TEMP_°Cnan20.0new
4019-1010222022-07-05Eurofins0.00.0TEMP_°Cnan16.0new
\n", "
" ], "text/plain": [ " vannmiljo_code sample_date lab depth1 depth2 par_unit flag value \\\n", "0 027-79278 2022-07-05 Eurofins 0.0 0.0 TEMP_°C nan 17.0 \n", "1 027-79278 2022-08-02 Eurofins 0.0 0.0 TEMP_°C nan 15.0 \n", "2 027-79278 2022-09-06 Eurofins 0.0 0.0 TEMP_°C nan 15.0 \n", "3 019-58793 2022-07-05 Eurofins 0.0 0.0 TEMP_°C nan 20.0 \n", "4 019-101022 2022-07-05 Eurofins 0.0 0.0 TEMP_°C nan 16.0 \n", "\n", " period \n", "0 new \n", "1 new \n", "2 new \n", "3 new \n", "4 new " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Read new data\n", "new_df = utils.read_data_template_to_wide(\n", " f\"../../data/{lab.lower()}_data_{year}_q{qtr}_v{version}.xlsx\",\n", " sheet_name=\"results\",\n", " lab=lab,\n", ")\n", "utils.perform_basic_checks(new_df)\n", "new_df = utils.wide_to_long(new_df, lab)\n", "\n", "# Add label for data period\n", "new_df[\"period\"] = \"new\"\n", "\n", "# Handle duplicates\n", "dup_csv = os.path.join(\n", " fold_path, f\"{lab.lower()}_{year}_q{qtr}_v{version}_duplicates.csv\"\n", ")\n", "new_df = utils.handle_duplicates(new_df, dup_csv, action=\"drop\")\n", "\n", "new_df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7 . Combine\n", "\n", "Combine the `historic` and `new` datasets into a single dataframe in \"long\" format." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vannmiljo_codesample_datelabdepth1depth2flagvalueperiodparameterunit
0019-444982012-01-02NIVA (historic)0.00.0=0.19historicKmg/l
1019-444982012-01-02NIVA (historic)0.00.0=1.71historicKONDmS/m
2019-444982012-02-15NIVA (historic)0.00.0=1.15historicCLmg/l
3019-444982012-02-15NIVA (historic)0.00.0=1.40historicKONDmS/m
4019-444982012-03-05NIVA (historic)0.00.0=1.83historicCLmg/l
\n", "
" ], "text/plain": [ " vannmiljo_code sample_date lab depth1 depth2 flag value \\\n", "0 019-44498 2012-01-02 NIVA (historic) 0.0 0.0 = 0.19 \n", "1 019-44498 2012-01-02 NIVA (historic) 0.0 0.0 = 1.71 \n", "2 019-44498 2012-02-15 NIVA (historic) 0.0 0.0 = 1.15 \n", "3 019-44498 2012-02-15 NIVA (historic) 0.0 0.0 = 1.40 \n", "4 019-44498 2012-03-05 NIVA (historic) 0.0 0.0 = 1.83 \n", "\n", " period parameter unit \n", "0 historic K mg/l \n", "1 historic KOND mS/m \n", "2 historic CL mg/l \n", "3 historic KOND mS/m \n", "4 historic CL mg/l " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Combine\n", "df = pd.concat([his_df, new_df], axis=\"rows\")\n", "\n", "# Separate par and unit\n", "df[[\"parameter\", \"unit\"]] = df[\"par_unit\"].str.split(\"_\", n=1, expand=True)\n", "del df[\"par_unit\"]\n", "\n", "df.reset_index(drop=True, inplace=True)\n", "\n", "df.head()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Apply correction to historic SIO2\n", "df[\"value\"] = np.where(\n", " (df[\"lab\"] == \"VestfoldLAB (historic)\") & (df[\"parameter\"] == \"SIO2\"),\n", " df[\"value\"] * 467.5432,\n", " df[\"value\"],\n", ")\n", "\n", "# Reclassify (nitrate + nitrite) to nitrate\n", "df[\"parameter\"].replace({\"N-SNOX\": \"N-NO3\"}, inplace=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. Check data ranges\n", "\n", "A simple method for preliminary quality control is to check whether parameter values are within sensible ranges (as defined in the `parameters_units` table; see Section 4 above). I believe this screening should be implemented differently for the `historic` (i.e. Vannmiljø) and `new` datsets, as follows:\n", "\n", " * For the `historic` data in Vannmiljø, values outside the plausible ranges should be **removed from the dataset entirely**. This is because we intend to use the Vannmiljø data as a reference against which new values will be compared, so it is important the dataset does not contain anything too strange. Ideally, the reference dataset should be carefully manually curated to ensure it is as good as possible, but I'm not sure we have the resouces in this project to thoroughly quality assess the data *already* in Vannmiljø. Dealing with any obvious issues is a good start, though\n", " \n", " * For the `new` data, values outside the plausible ranges should be highlighted and checked with the reporting lab\n", " \n", "**Note:** At present, my code will remove any concentration values of exactly zero from the historic dataset. **Check with Øyvind whether this is too strict**." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Checking data ranges for the 'historic' period.\n", " TEMP: Maximum value of 30.00 is greater than or equal to upper limit (30.00).\n", " PH: Minimum value of 0.10 is less than or equal to lower limit (1.00).\n", " KOND: Maximum value of 309.00 is greater than or equal to upper limit (100.00).\n", " P-TOT: Maximum value of 1200.00 is greater than or equal to upper limit (500.00).\n", " N-NO3: Maximum value of 2700.00 is greater than or equal to upper limit (2000.00).\n", " LAL: Minimum value of 0.00 is less than or equal to lower limit (0.00).\n", " SO4: Minimum value of 0.00 is less than or equal to lower limit (0.00).\n", " SO4: Maximum value of 58.00 is greater than or equal to upper limit (20.00).\n", " CA: Minimum value of 0.00 is less than or equal to lower limit (0.00).\n", " CA: Maximum value of 4800.00 is greater than or equal to upper limit (500.00).\n", " K: Maximum value of 23.00 is greater than or equal to upper limit (10.00).\n", " SIO2: Maximum value of 2763180.31 is greater than or equal to upper limit (7000.00).\n", " ANC: Minimum value of -1000.00 is less than or equal to lower limit (-1000.00).\n", "\n", "Checking data ranges for the 'new' period.\n", " ALK: Maximum value of 3.30 is greater than or equal to upper limit (2.00).\n", " LAL: Minimum value of 0.00 is less than or equal to lower limit (0.00).\n", "\n", "Dropping problem rows from historic data.\n", " Dropping rows for TEMP.\n", " Dropping rows for PH.\n", " Dropping rows for KOND.\n", " Dropping rows for P-TOT.\n", " Dropping rows for N-NO3.\n", " Dropping rows for LAL.\n", " Dropping rows for SO4.\n", " Dropping rows for CA.\n", " Dropping rows for K.\n", " Dropping rows for SIO2.\n", " Dropping rows for ANC.\n" ] } ], "source": [ "# Check ranges\n", "df = utils.check_data_ranges(df)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "196932" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Add to database\n", "df.to_sql(\n", " name=\"water_chemistry\",\n", " con=eng,\n", " if_exists=\"append\",\n", " index=False,\n", " method=\"multi\",\n", " chunksize=1000,\n", ")" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "eng.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9. Version 1 summary\n", "\n", "Points to note from the initial data exploration:\n", "\n", "### 9.1. Data formatting and missing data\n", "\n", " * Issues fixed in app\n", " \n", "### 9.2. Site IDs\n", "\n", " * No problems\n", " \n", "### 9.3. Duplicates\n", "\n", " * Duplicates were highlighted in the app and fixed by Eurofins\n", " \n", "### 9.4. Range checks\n", "\n", " * ALK: Maximum value of 3.30 mmol/l seems high" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 4 }