import pandas as pd
from marine_qc import (
duplicate_check,
flag_duplicates,
get_duplicates,
remove_duplicates,
)
from data import get_advanced_data
How to use the duplicate checker to detect possibly duplicated observations¶
The marine_qc toolbox provides some checks to detect possibly duplicated observations. Creating some test dataset we can show how to use them on “real” data. There are several checks that are shown here:
a duplicate check to detect possibly duplicated observations
a function that gets potentially duplicated observations and returns the corresponding detected duplicate matches for each observation
a function that flags potentially duplicated observations and returns the duplicate flags for each observation
a function that removes potentially duplicated observations and returns the input data with duplicates excluded
For more information of all available QC checks on individual reports see the overview of QC functions to detect possibly duplicated observations.
The test dataset we provide is a pandas.DataFrame including several individual marine reports.
data = get_advanced_data()
data
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 |
We have several different marine reports that include six different parameters (lat, lon, date, sst (sea-surface temperature), vsi (ship speed), and dsi (ship heading)).
First of all, we detect possibly duplicated observations. This function returns a marine_qc.duplicate_checker.duplicates.DupDetect class that contains all information needed for the other duplicate functions.
Use the default settings of the duplicate checker¶
To introduce the duplicate check functions, we work with the default settings.
Note:
By default, the duplicate check comprises six kinds of observational values:
station_id: Any kind of idnetifier of the observing station (e.g. ship name, WIGOS Station Identifier).
lat: Latitude of the observing station in degrees.
lon: Longitude of the observing station in degrees.
date: Time stamp of the obersing station (datetime object or datetime-formatted string).
vsi: Speed of the observing station in kilometers per hour.
dsi: Course of the observing station in degrees.By default, duplicate observations are detected according to the following criteria:
station_id: values must match exactly for observations to be considered duplicates.
lat: values may differ by up to 0.11 degrees for observations to be considered duplicates.
lon: values may differ by up to 0.11 degrees for observations to be considered duplicates.
date: values may differ by up to 1 minute for observations to be considered duplicates.
vsi: values may differ by up to 0.09 kilometers per hour for observations to be considered duplicates.
dsi: values may differ by up to 0.9 degrees for observations to be considered duplicates.
Later, we will see how to add extra kinds of observational values and their corresponding criteria.
Firstly, we run the duplicate_check. The result can be used to run the other duplicate check functions. Alternatively, the functions can be run directly with the data itself. If so, duplicate_check is run internally.
detected = duplicate_check(data=data)
dups = get_duplicates(
detected=detected,
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | H |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, N] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
As we can see, the following duplicates has been detected:
data.loc[["B", "H"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| B | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 |
| H | AA | 29.7 | 71.3 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 |
We expect these to be detected as duplicates since:
station_id: do match exactlylon: do match exactlylat: do match exactlydate: differ less than 1 minutevsi: do match exactlydsi: do match exactly
data.loc[["C", "J"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| C | AA | 32.0 | 71.2 | 2022-02-01 | NaN | NaN | 300.0 |
| J | AA | 32.0 | 71.2 | 2022-02-01 | NaN | NaN | 300.0 |
We expect these to be detected as duplicates since:
station_id: do match exactlylon: do match exactlylat: do match exactlydate: do match exactlyvsi: are bothNaNdsi: are bothNaN
data.loc[["D", "L", "N"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| D | AA | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
We expect these to be detected as duplicates since:
station_id: do match exactlylon: differ less than 0.11 degreeslat: differ less than 0.11 degreesdate: do match exactlyvsi: do match exactlydsi: do match exactly
data.loc[["E", "F", "Q"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| E | AA | -21.2 | 65.8 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| F | AA | -21.2 | 65.8 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| Q | AA | -21.1 | 65.9 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
We expect these to be detected as duplicates since:
station_id: do match exactlylon: differ less than 0.11 degreeslat: differ less than 0.11 degreesdate: do match exactlyvsi: do match exactlydsi: do match exactly
Now, we can flag the duplicates. The flagging scheme is:
0: unique observation1: best duplicate3: worst duplicate
flags = flag_duplicates(detected=detected)
pd.DataFrame({**data, "duplicate_flag": flags})
| station_id | lon | lat | date | vsi | dsi | sst | duplicate_flag | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | 1 |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | 1 |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 1 |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 1 |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 3 |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | 3 |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | 0 |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | 3 |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 3 |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 3 |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 0 |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | 3 |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | 0 |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | 0 |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | 0 |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | 0 |
Optionally, we can remove the duplicated observations.
series = remove_duplicates(detected=detected)
pd.DataFrame(series).T
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| A | AA | 22.3 | 71.3 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| B | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 |
| C | AA | 32.0 | 71.2 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 |
| D | AA | 8.1 | 66.0 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| E | AA | -21.2 | 65.8 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| G | AA | 21.2 | -65.8 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| I | AA | 29.7 | 71.3 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 |
| K | AA | 8.5 | 66.0 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| M | AB | 8.1 | 66.0 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| O | BB | 8.1 | 66.0 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| P | AA | -21.4 | 65.6 | 2022-02-01 00:00:00 | 0.0 | 0.0 | 300.0 |
| R | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 |
| S | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.0 | 315.0 | 300.0 |
| T | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 |
| U | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 |
Manipulate the settings of the duplicate checker¶
Here are some more examples of how to use the duplicate checker with user-specific settings.
ignore_entires: Ignore specific values for selected columns during comparison.ignore_nan_both: For selected columns, consider two observations as duplicates whenever both compared value are NaN. By default, this is true for all columns.ignore_nan_either: For selected columns, consider two observations as duplicates whenever either compared value is NaN.ignore_columns: Column names to exclude entirely from duplicate detection.offsets: Override comparison offsets for selected columns.compare_level_libraries: Override comparison levels for selected columns. For more information see Splink: Comparison Level Library.
ignore_entries¶
If station_id of an observation is “AA” or “BB”, this entry is considered placeholder which is compatible with and equivalent to any other station_id value.
dups = get_duplicates(
data=data,
ignore_entries={"station_id": ["AA", "BB"]},
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | H |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, M, N, O] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
In addition to the default example, we get the following duplicates:
data.loc[["D", "L", "M", "N", "O"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| D | AA | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| M | AB | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| O | BB | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
“O” is detected as duplicate as expected since we use “BB” as a placeholder.
“M” is detected as duplicate as expected since we use “AA” and “BB” as placeholders which makes “AB” the only valid
station_id.
ignore_nan_both¶
Two observations are considered as duplicates whenever both compared value are NaN only for selected column vsi.
dups = get_duplicates(
data=data,
ignore_nan_both="vsi",
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | H |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | NaN |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, N] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | NaN |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
In comparison with the default example “C” and “J” are not considered as duplicates since dsi has NaN values:
data.loc[["C", "J"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| C | AA | 32.0 | 71.2 | 2022-02-01 | NaN | NaN | 300.0 |
| J | AA | 32.0 | 71.2 | 2022-02-01 | NaN | NaN | 300.0 |
ignore_nan_either¶
For vsi, consider two observations as duplicates whenever either compared value is NaN.
dups = get_duplicates(
data=data,
ignore_nan_either="vsi",
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | [H, U, S] |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, N] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | B |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | B |
The duplicate group (“B”, “H”) is extended by (“U”, “S”).
data.loc[["B", "H", "U", "S"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| B | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 |
| H | AA | 29.7 | 71.3 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 |
| U | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 |
| S | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 |
Here we can see, that ignore_nan_either can lead to misleading duplicate chains:
(“B”, “H”) is obviously a duplicate pair.
(“H”, “U”) is a duplicate pair as well since we ignore NaNs for
vsi.(“U”, “S”) is a duplicate pair as well since we ignore NaNs for
vsi.
This leads to the following duplicate chain:
“B” -> “H” -> “U” -> “S”
But obviously, (“B”, “S”) is not a duplicate pair since vsi differs more than 0.11 degrees.
ignore_columns¶
We exclude station_id entirely from duplicate detection.
dups = get_duplicates(
data=data,
ignore_columns="station_id",
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | H |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, M, N, O] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
We extend one duplicate group from the default example since we ignore station_id completely.
data.loc[["D", "L", "M", "N", "O"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| D | AA | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| M | AB | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| O | BB | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
offsets¶
We now consider two observations as duplicates if:
latdiffers up to 1.0 degreeslondiffers up to 1.0 degreesdatediffers up to 360 seconds (1 hour)
dups = get_duplicates(
data=data,
offsets={"lat": 1.0, "lon": 1.0, "date": 360},
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | H |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [K, L, N] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, P, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | B |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
We expand two duplicate groups:
data.loc[["D", "K", "L", "N"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| D | AA | 8.10 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| K | AA | 8.50 | 66.00 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| L | AA | 8.15 | 66.05 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| N | AA | 8.05 | 65.95 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
data.loc[["E", "F", "P", "Q"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| E | AA | -21.2 | 65.8 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| F | AA | -21.2 | 65.8 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| P | AA | -21.4 | 65.6 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
| Q | AA | -21.1 | 65.9 | 2022-02-01 | 0.0 | 0.0 | 300.0 |
compare_level_libraries¶
Finally, we add sst with an offset of 1K to the criteria list.
dups = get_duplicates(
data=data,
compare_level_libraries={"sst": "AbsoluteDifferenceLevel"},
offsets={"sst": 1.0},
)
pd.DataFrame({**data, "duplicates": dups})
| station_id | lon | lat | date | vsi | dsi | sst | duplicates | |
|---|---|---|---|---|---|---|---|---|
| A | AA | 22.30 | 71.30 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| B | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 | NaN |
| C | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | J |
| D | AA | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [L, N] |
| E | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | [F, Q] |
| F | AA | -21.20 | 65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| G | AA | 21.20 | -65.80 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| H | AA | 29.70 | 71.30 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 | NaN |
| I | AA | 29.70 | 71.30 | 2022-02-02 00:00:00 | 4.11552 | 315.0 | 300.0 | NaN |
| J | AA | 32.00 | 71.20 | 2022-02-01 00:00:00 | NaN | NaN | 300.0 | C |
| K | AA | 8.50 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| L | AA | 8.15 | 66.05 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| M | AB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| N | AA | 8.05 | 65.95 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | D |
| O | BB | 8.10 | 66.00 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| P | AA | -21.40 | 65.60 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | NaN |
| Q | AA | -21.10 | 65.90 | 2022-02-01 00:00:00 | 0.00000 | 0.0 | 300.0 | E |
| R | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | 316.0 | 300.0 | NaN |
| S | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.00000 | 315.0 | 300.0 | NaN |
| T | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | 4.11552 | NaN | 300.0 | NaN |
| U | AA | 29.70 | 71.30 | 2022-02-01 00:00:00 | NaN | 315.0 | 300.0 | NaN |
Now (“B”, “H”) is no duplicate pair anymore since sst differs more than 1 Kelvin.
data.loc[["B", "H"]]
| station_id | lon | lat | date | vsi | dsi | sst | |
|---|---|---|---|---|---|---|---|
| B | AA | 29.7 | 71.3 | 2022-02-01 00:00:00 | 4.11552 | 315.0 | 302.0 |
| H | AA | 29.7 | 71.3 | 2022-02-01 00:00:59 | 4.11552 | 315.0 | 300.0 |