Frequency Editing, Resampling, And QC#

pycsamt.emtools.frequency is the user-guide hub for operations that change, evaluate, or visualize the frequency axis. It is used before inversion, before section plotting, after quality control, and whenever multiple stations need to share a consistent frequency grid.

The module covers four practical jobs:

  • selecting a usable frequency band;

  • dropping, masking, or recovering low-confidence rows;

  • regridding, decimating, smoothing, and aligning station grids;

  • plotting frequency coverage, apparent depth, and band-level summary diagnostics.

Full callable signatures live in the API reference. This page focuses on how to use the workflows safely, how to read their tables, and how to avoid common frequency-axis traps.

Why Frequency Editing Matters#

CSAMT/AMT/MT processing is rarely limited by one bad station value. More often, a workflow succeeds or fails because the frequency axis is inconsistent, too noisy at the band edges, or incompatible across stations. A 2-D inversion, pseudo-section, or model-comparison workflow needs clear decisions about:

  • which band is physically meaningful;

  • which frequency rows are trusted;

  • whether bad rows are removed or kept as missing values;

  • whether stations should be interpolated to a shared grid;

  • whether smoothing or decimation is acceptable for the next step.

The frequency module makes those decisions explicit and auditable.

Data Contract#

All editing functions accept the usual emtools input:

  • a directory containing EDI files,

  • one EDI-like object,

  • a Sites container,

  • an iterable of site-like objects.

Internally the functions call ensure_sites. The familiar recursive, on_dup, strict, and verbose arguments behave the same way as in the rest of the user guide.

Most editing functions default to inplace=False. Keep that default while exploring. Switch to inplace=True only when you intentionally want to mutate the input object.

Selecting A Band#

Use select_band when you already know the frequency limits.

 1from pathlib import Path
 2
 3from pycsamt.emtools.frequency import select_band
 4
 5edi_dir = Path("data/AMT/WILLY_DATA/L18PLT")
 6
 7mid_band = select_band(
 8    edi_dir,
 9    fmin=10.0,
10    fmax=1000.0,
11    inplace=False,
12)

fmin and fmax are in hertz. The convenience argument band_hz=(10.0, 1000.0) is also accepted. If both forms are provided, explicit fmin and fmax take precedence.

Use keep when you want specific frequencies rather than a continuous band.

1from pycsamt.emtools.frequency import select_band
2
3selected = select_band(
4    "data/AMT/WILLY_DATA/L18PLT",
5    keep=[10.0, 100.0, 1000.0],
6)

Band selection removes rows outside the chosen band. It changes the frequency grid length.

Removing Duplicate Frequencies#

Use drop_duplicates when a station has repeated or unsorted frequency rows.

1from pycsamt.emtools.frequency import drop_duplicates
2
3cleaned = drop_duplicates(
4    "data/AMT/WILLY_DATA/L18PLT",
5    tol=1e-10,
6    inplace=False,
7)

The function keeps the first occurrence of each frequency within the tolerance and applies the same row selection to impedance and tipper blocks when present.

Frequency Confidence#

Confidence-based editing uses frequency_confidence_table from the QC workflow. The frequency module computes that table internally, but you should inspect it before editing.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools.qc import frequency_confidence_table
 4
 5survey = "data/AMT/WILLY_DATA/L18PLT"
 6confidence = frequency_confidence_table(survey)
 7
 8print(confidence[["station", "frequency_hz", "confidence", "flags"]].head())
 9print(confidence["confidence"].describe())
10
11station = "18-001A"
12one = confidence.loc[confidence["station"] == station].sort_values("period_s")
13
14fig, ax = plt.subplots(figsize=(7, 4.5))
15ax.semilogx(one["period_s"], one["confidence"], "o-")
16ax.axhline(0.90, color="tab:red", linestyle="--", label="ci_hi")
17ax.axhline(0.50, color="tab:orange", linestyle="--", label="ci_lo")
18ax.set_xlabel("Period (s)")
19ax.set_ylabel("Confidence")
20ax.legend()
21fig.tight_layout()
   station  ...                                              flags
0  18-001A  ...  recoverable,high_error,offdiag_mismatch,diagon...
1  18-001A  ...  reject,high_error,offdiag_mismatch,diagonal_le...
2  18-001A  ...  reject,high_error,offdiag_mismatch,diagonal_le...
3  18-001A  ...  reject,high_error,offdiag_mismatch,diagonal_le...
4  18-001A  ...  reject,high_error,offdiag_mismatch,diagonal_le...

[5 rows x 4 columns]
count    1484.000000
mean        0.658116
std         0.096143
min         0.417464
25%         0.586675
50%         0.654938
75%         0.736039
max         0.863041
Name: confidence, dtype: float64
../../_images/user-guide-emtools-frequency-04.png

The key lesson is simple: do not choose ci_hi or threshold before looking at the confidence distribution. If no row reaches ci_hi, recovery has no trusted rows to interpolate from.

Drop Or Mask Low-Confidence Rows#

Dropping removes rows from the frequency grid. Masking keeps the grid but replaces low-confidence tensor rows with NaN.

 1from pycsamt.emtools.frequency import (
 2    drop_low_confidence_frequencies,
 3    mask_low_confidence_frequencies,
 4)
 5
 6survey = "data/AMT/WILLY_DATA/L18PLT"
 7
 8dropped = drop_low_confidence_frequencies(
 9    survey,
10    threshold=0.50,
11    also="both",
12    inplace=False,
13)
14
15masked = mask_low_confidence_frequencies(
16    survey,
17    threshold=0.50,
18    also="both",
19    inplace=False,
20)

Use drop when downstream code cannot handle missing rows. Use mask when preserving the original frequency grid matters, for example when comparing before and after processing.

The also argument controls which blocks are edited:

Value

Meaning

"z"

Edit impedance only.

"tipper"

Edit tipper only.

"both"

Edit impedance and tipper when present.

Recover Low-Confidence Rows#

recover_low_confidence_frequencies treats rows in [ci_lo, ci_hi) as recoverable and interpolates them from trusted rows whose confidence is at least ci_hi. Rows below ci_lo are handled by reject.

 1from pycsamt.emtools.frequency import recover_low_confidence_frequencies
 2
 3recovered = recover_low_confidence_frequencies(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    ci_hi=0.72,
 6    ci_lo=0.50,
 7    interpolation="linear",
 8    reject="drop",
 9    also="both",
10    inplace=False,
11)

Valid interpolation modes are "linear" and "nearest". Valid reject modes are:

Reject mode

Meaning

"drop"

Remove rows below ci_lo.

"mask"

Keep rows below ci_lo but set them to NaN.

"keep"

Leave rejected rows unchanged.

Recovery is interpolation, not magic. At band edges, interpolation can behave like holding the nearest trusted value. Always inspect the result when many rows are recovered.

High-Level Editing Workflow#

Use edit_frequencies_by_confidence when you want the edited sites, a station-level report, and a row-level decision table in one object.

 1from pycsamt.emtools.frequency import edit_frequencies_by_confidence
 2
 3result = edit_frequencies_by_confidence(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    mode="recover",
 6    ci_hi=0.72,
 7    ci_lo=0.50,
 8    reject="drop",
 9    interpolation="linear",
10    api=False,
11)
12
13print(result.summary())
14print(result.report.head())
15print(result.decisions.head())
FrequencyEditResult(mode='recover', method='composite', dropped=73, masked=102, recovered=867)
   station  n_freq_before  ...  n_masked_or_unfinite  confidence_delta
0  18-001A             53  ...                     0          0.027413
1  18-002U             53  ...                     0         -0.038885
2  18-003A             53  ...                     3          0.061644
3  18-004A             53  ...                     3         -0.026442
4  18-005U             53  ...                     1          0.026781

[5 rows x 18 columns]
   station  frequency_hz  period_s  ...  present_after  finite_after action
0  18-001A       10400.0  0.000096  ...           True          True   kept
1  18-001A        8707.0  0.000115  ...           True          True   kept
2  18-001A        7289.0  0.000137  ...           True          True   kept
3  18-001A        6102.0  0.000164  ...           True          True   kept
4  18-001A        5108.0  0.000196  ...           True          True   kept

[5 rows x 10 columns]

result is a FrequencyEditResult with:

  • sites: edited sites.

  • report: station-level before/after table.

  • decisions: one row per original station-frequency row.

  • n_dropped, n_masked, n_recovered: convenience counts.

  • mode, method, ci_hi, ci_lo, reject, interpolation: edit metadata.

For in-memory objects, pass an independent before_sites when you need an untouched baseline for reporting. Some lower-level editors may mutate wrapped impedance objects while constructing edited outputs.

Station-Level Report#

frequency_edit_report compares before and after sites.

 1from pycsamt.emtools.frequency import (
 2    edit_frequencies_by_confidence,
 3    frequency_edit_report,
 4)
 5
 6survey = "data/AMT/WILLY_DATA/L18PLT"
 7result = edit_frequencies_by_confidence(
 8    survey,
 9    mode="drop",
10    threshold=0.50,
11    api=False,
12)
13
14report = frequency_edit_report(
15    survey,
16    result.sites,
17    ci_hi=0.90,
18    ci_lo=0.50,
19)
20
21print(
22    report[
23        [
24            "station",
25            "n_freq_before",
26            "n_freq_after",
27            "n_dropped",
28            "n_finite_before",
29            "n_finite_after",
30            "confidence_median_before",
31            "confidence_median_after",
32        ]
33    ].head()
34)
   station  n_freq_before  ...  confidence_median_before  confidence_median_after
0  18-001A             53  ...                  0.711753                 0.711753
1  18-002U             53  ...                  0.749480                 0.749480
2  18-003A             53  ...                  0.666613                 0.671639
3  18-004A             53  ...                  0.735994                 0.743510
4  18-005U             53  ...                  0.728841                 0.733250

[5 rows x 8 columns]

Important report columns include:

  • n_freq_before and n_freq_after: grid size change.

  • n_finite_before and n_finite_after: finite tensor row counts.

  • frac_finite_before and frac_finite_after: finite row fraction.

  • safe_fraction_*: fraction at or above ci_hi.

  • recoverable_fraction_*: fraction in [ci_lo, ci_hi).

  • reject_fraction_*: fraction below ci_lo.

  • n_dropped: number of removed frequency rows.

  • n_masked_or_unfinite: finite rows lost to masking or missing data.

  • confidence_delta: median confidence change.

Decision Table#

frequency_edit_decision_table records one row per original station-frequency sample.

 1from pycsamt.emtools.frequency import (
 2    edit_frequencies_by_confidence,
 3    frequency_edit_decision_table,
 4)
 5
 6survey = "data/AMT/WILLY_DATA/L18PLT"
 7result = edit_frequencies_by_confidence(
 8    survey,
 9    mode="recover",
10    ci_hi=0.72,
11    ci_lo=0.50,
12    reject="drop",
13    api=False,
14)
15
16decisions = frequency_edit_decision_table(
17    survey,
18    result.sites,
19    ci_hi=0.72,
20    ci_lo=0.50,
21)
22
23print(decisions["action"].value_counts())
action
recovered    867
kept         442
masked       102
dropped       73
Name: count, dtype: int64

Actions are:

  • kept: row survived unchanged.

  • dropped: row is absent after editing.

  • masked: row remains but is no longer finite.

  • recovered: row was filled or changed by recovery.

Plot Edit Results#

Use the summary plot for station-level effects and the decision plot for row-level actions.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools.frequency import (
 4    edit_frequencies_by_confidence,
 5    plot_frequency_edit_decisions,
 6    plot_frequency_edit_summary,
 7)
 8
 9survey = "data/AMT/WILLY_DATA/L18PLT"
10result = edit_frequencies_by_confidence(
11    survey,
12    mode="recover",
13    ci_hi=0.72,
14    ci_lo=0.50,
15    reject="drop",
16    api=False,
17)
18
19fig, (ax_summary, ax_decisions) = plt.subplots(2, 1, figsize=(10, 8))
20plot_frequency_edit_summary(survey, result.sites, ci_hi=0.72, ci_lo=0.50, ax=ax_summary)
21plot_frequency_edit_decisions(survey, result.sites, ci_hi=0.72, ci_lo=0.50, ax=ax_decisions)
22fig.tight_layout()
../../_images/user-guide-emtools-frequency-10.png

If the decision plot is dominated by recovered or masked colors, the edit is not conservative and should be justified.

Regrid To A Target Grid#

Use regrid_to when you already have the target frequencies.

 1import numpy as np
 2
 3from pycsamt.emtools.frequency import regrid_to
 4
 5target_freq = np.array([1.0, 3.0, 10.0, 30.0, 100.0, 300.0, 1000.0])
 6
 7regridded = regrid_to(
 8    "data/AMT/WILLY_DATA/L18PLT",
 9    target_freq,
10    method="linear",
11    inplace=False,
12)

method controls interpolation behavior. The implementation supports nearest-neighbor and log-frequency interpolation paths used by the module’s helpers. Use a target grid that is meaningful for your inversion or comparison, not merely convenient.

Build A Log-Spaced Grid#

Use regrid_logspace to generate a grid automatically.

 1from pycsamt.emtools.frequency import regrid_logspace
 2
 3regular = regrid_logspace(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    fmin=1.0,
 6    fmax=10000.0,
 7    per_decade=6,
 8    method="linear",
 9    inplace=False,
10)

band_hz=(lo, hi) is accepted as an alias for fmin and fmax. n_per_decade is accepted as an alias for per_decade when per_decade is left at its default.

Decimation And Moving Average Smoothing#

Use decimate_step to keep every step row. Use smooth_mavg to apply a moving average along frequency.

 1from pycsamt.emtools.frequency import decimate_step, smooth_mavg
 2
 3decimated = decimate_step(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    step=2,
 6    inplace=False,
 7)
 8
 9smoothed = smooth_mavg(
10    "data/AMT/WILLY_DATA/L18PLT",
11    k=3,
12    on="z",
13    inplace=False,
14)

Smoothing changes the measured tensor values. Use it as a processing choice, not as a plotting convenience, and report the window size.

Align Station Grids#

Use align_grid when stations need a common grid.

 1from pycsamt.emtools.frequency import align_grid
 2
 3union_aligned = align_grid(
 4    "data/AMT/WILLY_DATA/L18PLT",
 5    mode="union",
 6    method="nearest",
 7)
 8
 9intersection_aligned = align_grid(
10    "data/AMT/WILLY_DATA/L18PLT",
11    mode="intersection",
12    method="nearest",
13)
14
15ref_aligned = align_grid(
16    "data/AMT/WILLY_DATA/L18PLT",
17    mode="ref",
18    ref_station="18-001A",
19    method="nearest",
20)

The modes are:

Mode

Meaning

union

Use all frequencies present in any station.

intersection

Use only frequencies present in every station.

ref

Use the grid from ref_station.

intersection can be empty or nearly empty for independently processed data because frequencies may not match bit-for-bit. If the intersection is empty, the function returns the input sites unchanged.

Coverage And Quality Heatmap#

plot_coverage_quality_heatmap shows which station-frequency cells exist and how reliable they are according to relative impedance error.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools.frequency import plot_coverage_quality_heatmap
 4
 5fig, ax = plt.subplots(figsize=(9, 4.5))
 6plot_coverage_quality_heatmap(
 7    "data/AMT/WILLY_DATA/L18PLT",
 8    axis="period",
 9    ax=ax,
10)
11fig.tight_layout()
../../_images/user-guide-emtools-frequency-15.png

The color is 1 / (1 + relative_error). Values closer to 1 are higher quality. Missing cells indicate no frequency row on the union grid for that station.

Apparent Depth Pseudo-Section#

plot_apparent_depth_psection converts determinant apparent resistivity and frequency into a skin-depth-style apparent depth:

\[depth \approx 503 \sqrt{\rho / f}\]
 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools.frequency import plot_apparent_depth_psection
 4
 5fig, ax = plt.subplots(figsize=(9, 4.5))
 6plot_apparent_depth_psection(
 7    "data/AMT/WILLY_DATA/L18PLT",
 8    axis_y="period",
 9    agg="median",
10    log_color=True,
11    ax=ax,
12)
13fig.tight_layout()
../../_images/user-guide-emtools-frequency-16.png

This is a diagnostic visualization, not an inversion result. Use it to see how apparent depth varies across stations and periods.

Band Microstrips#

plot_band_microstrips collapses periods into bands and plots three summary metrics per station and band:

  • median log10 determinant apparent resistivity;

  • median determinant phase;

  • median tipper amplitude when available.

 1import matplotlib.pyplot as plt
 2
 3from pycsamt.emtools.frequency import plot_band_microstrips
 4
 5bands = [
 6    (1e-4, 1e-3),
 7    (1e-3, 1e-2),
 8    (1e-2, 1e-1),
 9    (1e-1, 1.0),
10]
11
12fig, ax = plt.subplots(figsize=(9, 6))
13plot_band_microstrips(
14    "data/AMT/WILLY_DATA/L18PLT",
15    bands=bands,
16    ax=ax,
17)
18fig.tight_layout()
../../_images/user-guide-emtools-frequency-17.png

Use microstrips for compact survey summaries. They are not a substitute for full curves when a band contains strong internal variation.

Common Failure Modes#

Using default ci_hi without checking confidence

Recovery needs trusted rows at or above ci_hi. If the survey confidence never reaches that value, recovery cannot behave as expected.

Dropping rows before checking downstream needs

Dropping shortens the grid. If the next step expects aligned grids, masking may be safer.

Masking rows before plotting finite-only workflows

Some workflows ignore or fail on NaN rows. Save a decision table so missing values are explainable.

Regridding too early

Interpolation can hide native acquisition problems. Inspect native confidence first.

Empty intersection alignment

Independently processed stations may have no exact shared frequency. Use union or a reference station when appropriate.

Smoothing as hidden preprocessing

Moving averages change tensor values. Report the window and apply it intentionally.

Worked Example#

The gallery example uses L18PLT from data/AMT/WILLY_DATA/ and brings in KAP03 for heterogeneous-grid alignment. It demonstrates band selection, confidence editing, recovery, reports, decision plots, regridding, decimation, smoothing, grid alignment, coverage heatmaps, apparent-depth pseudo-sections, and band microstrips.

Open the rendered example here: Frequency editing, resampling, and QC (pycsamt.emtools.frequency).