Pandas Integration Example for ansys-units#

This example demonstrates how to use ansys-units with pandas DataFrames for unit-aware data analysis.

from typing import TypeAlias

import numpy as np
import pandas as pd
from pandas import DataFrame, Series

from ansys.units import Quantity
from ansys.units.extensions.pandas_extension import QuantityArray, QuantityDtype

Basic Usage#

Create Series with units

print("=" * 70)
print("1. Creating Unit-Aware Series")
print("=" * 70)

# Method 1: Using dtype string
distances: Series = Series([1.0, 2.0, 3.0, 4.0, 5.0], dtype="quantity[m]")
print("\nDistances in meters:")
print(distances)
print(f"Dtype: {distances.dtype}")

# Method 2: Using QuantityDtype object
mass_dtype: QuantityDtype = QuantityDtype("kg")
masses: Series = Series([10.0, 20.0, 30.0, 40.0, 50.0], dtype=mass_dtype)
print("\nMasses in kilograms:")
print(masses)
======================================================================
1. Creating Unit-Aware Series
======================================================================

Distances in meters:
0    1.0 m
1    2.0 m
2    3.0 m
3    4.0 m
4    5.0 m
dtype: quantity[m]
Dtype: quantity[m]

Masses in kilograms:
0    10.0 kg
1    20.0 kg
2    30.0 kg
3    40.0 kg
4    50.0 kg
dtype: quantity[kg]

Unit Conversion#

Convert between different units

print("\n" + "=" * 70)
print("2. Unit Conversion")
print("=" * 70)

# Convert meters to feet
distances_ft: Series = distances.units.to("ft")
print("\nDistances converted to feet:")
print(distances_ft)
print(f"Dtype: {distances_ft.dtype}")

# Convert meters to centimeters
distances_cm: Series = distances.units.to("cm")
print("\nDistances converted to centimeters:")
print(distances_cm)

# Convert kilograms to grams
masses_g: Series = masses.units.to("g")
print("\nMasses converted to grams:")
print(masses_g)
======================================================================
2. Unit Conversion
======================================================================

Distances converted to feet:
0     3.280839895013124 ft
1     6.561679790026248 ft
2     9.842519685039372 ft
3    13.123359580052496 ft
4    16.404199475065617 ft
dtype: quantity[ft]
Dtype: quantity[ft]

Distances converted to centimeters:
0    100.0 cm
1    200.0 cm
2    300.0 cm
3    400.0 cm
4    500.0 cm
dtype: quantity[cm]

Masses converted to grams:
0    10000.0 g
1    20000.0 g
2    30000.0 g
3    40000.0 g
4    50000.0 g
dtype: quantity[g]

DataFrame Operations#

Working with multiple unit columns

print("\n" + "=" * 70)
print("3. Unit-Aware DataFrame")
print("=" * 70)

# Create DataFrame with multiple unit columns
df = DataFrame(
    {
        "specimen_id": [1, 2, 3, 4, 5],
        "length": Series([10.0, 20.0, 30.0, 40.0, 50.0], dtype="quantity[mm]"),
        "width": Series([5.0, 5.0, 5.0, 5.0, 5.0], dtype="quantity[mm]"),
        "thickness": Series([2.0, 2.0, 2.0, 2.0, 2.0], dtype="quantity[mm]"),
        "mass": Series([10.0, 20.0, 30.0, 40.0, 50.0], dtype="quantity[g]"),
        "force": Series([100.0, 200.0, 300.0, 400.0, 500.0], dtype="quantity[N]"),
    }
)

print("\nOriginal DataFrame:")
print(df)

# Get units summary
print("\nUnits Summary:")
units_summary = df.units.summary()
for col, unit in units_summary.items():
    print(f"  {col}: {unit}")
======================================================================
3. Unit-Aware DataFrame
======================================================================

Original DataFrame:
   specimen_id   length   width  thickness    mass    force
0            1  10.0 mm  5.0 mm     2.0 mm  10.0 g  100.0 N
1            2  20.0 mm  5.0 mm     2.0 mm  20.0 g  200.0 N
2            3  30.0 mm  5.0 mm     2.0 mm  30.0 g  300.0 N
3            4  40.0 mm  5.0 mm     2.0 mm  40.0 g  400.0 N
4            5  50.0 mm  5.0 mm     2.0 mm  50.0 g  500.0 N

Units Summary:
  length: mm
  width: mm
  thickness: mm
  mass: g
  force: N

Batch Unit Conversion#

Convert multiple columns at once

print("\n" + "=" * 70)
print("4. Batch Unit Conversion")
print("=" * 70)

# Convert to different unit system
df_converted: DataFrame = df.units.to(
    {"length": "cm", "width": "cm", "thickness": "cm", "mass": "kg"}
)

print("\nDataFrame with converted units:")
print(df_converted)

print("\nNew Units Summary:")
units_summary = df_converted.units.summary()
for col, unit in units_summary.items():
    print(f"  {col}: {unit}")
======================================================================
4. Batch Unit Conversion
======================================================================

DataFrame with converted units:
   specimen_id  length   width  thickness     mass    force
0            1  1.0 cm  0.5 cm     0.2 cm  0.01 kg  100.0 N
1            2  2.0 cm  0.5 cm     0.2 cm  0.02 kg  200.0 N
2            3  3.0 cm  0.5 cm     0.2 cm  0.03 kg  300.0 N
3            4  4.0 cm  0.5 cm     0.2 cm  0.04 kg  400.0 N
4            5  5.0 cm  0.5 cm     0.2 cm  0.05 kg  500.0 N

New Units Summary:
  length: cm
  width: cm
  thickness: cm
  mass: kg
  force: N

Creating from Quantity Objects#

Build arrays from existing Quantity objects

print("\n" + "=" * 70)
print("5. Creating from Quantity Objects")
print("=" * 70)

# Create list of Quantity objects
quantities = [
    Quantity(1.0, "m"),
    Quantity(100.0, "cm"),  # Will be converted to meters
    Quantity(1000.0, "mm"),  # Will be converted to meters
]

# Create array from quantities (they get normalized)
arr = QuantityArray._from_sequence(quantities)
series: Series = Series(arr)

print("\nSeries created from mixed-unit Quantities (normalized to first unit):")
print(series)
print(f"All values in: {series.dtype.units}")
======================================================================
5. Creating from Quantity Objects
======================================================================

Series created from mixed-unit Quantities (normalized to first unit):
0    1.0 m
1    1.0 m
2    1.0 m
dtype: quantity[m]
All values in: m

Indexing and Selection#

Access individual elements and slices

print("\n" + "=" * 70)
print("6. Indexing and Selection")
print("=" * 70)

# Get single element (returns Quantity)
first_distance: Series = distances.iloc[0]
print(f"\nFirst distance: {first_distance}")
print(f"Type: {type(first_distance)}")
print(f"Value: {first_distance.value} {first_distance.units}")

# Get slice (returns Series)
middle_distances: Series = distances.iloc[1:4]
print("\nMiddle distances (indices 1-3):")
print(middle_distances)
print(f"Type: {type(middle_distances)}")

# Boolean indexing
large_masses: Series = masses[masses.units.quantity.value > 25.0]
print("\nMasses greater than 25 kg:")
print(large_masses)
======================================================================
6. Indexing and Selection
======================================================================

First distance: 1.0 m
Type: <class 'ansys.units.quantity.Quantity'>
Value: 1.0 m

Middle distances (indices 1-3):
1    2.0 m
2    3.0 m
3    4.0 m
dtype: quantity[m]
Type: <class 'pandas.Series'>

Masses greater than 25 kg:
2    30.0 kg
3    40.0 kg
4    50.0 kg
dtype: quantity[kg]

Handling Missing Values#

Working with NaN values

print("\n" + "=" * 70)
print("7. Missing Values")
print("=" * 70)

# Create series with NaN
data_with_nan: Series = Series(
    [1.0, 2.0, np.nan, 4.0, 5.0], dtype="quantity[m]"
)  # noqa: E501
print("\nData with missing values:")
print(data_with_nan)

# Check for NaN
print("\nMissing value mask:")
print(data_with_nan.isna())

# Drop NaN values
clean_data: Series = data_with_nan.dropna()
print("\nData after dropping NaN:")
print(clean_data)
======================================================================
7. Missing Values
======================================================================

Data with missing values:
0    1.0 m
1    2.0 m
2     <NA>
3    4.0 m
4    5.0 m
dtype: quantity[m]

Missing value mask:
0    False
1    False
2     True
3    False
4    False
dtype: bool

Data after dropping NaN:
0    1.0 m
1    2.0 m
3    4.0 m
4    5.0 m
dtype: quantity[m]

Concatenation#

Combining Series with units

print("\n" + "=" * 70)
print("8. Concatenation")
print("=" * 70)

s1: Series = Series([1.0, 2.0, 3.0], dtype="quantity[m]")
s2: Series = Series([4.0, 5.0, 6.0], dtype="quantity[m]")

combined: Series = pd.concat([s1, s2], ignore_index=True)
print("\nConcatenated Series:")
print(combined)
print(f"Dtype preserved: {combined.dtype}")
======================================================================
8. Concatenation
======================================================================

Concatenated Series:
0    1.0 m
1    2.0 m
2    3.0 m
3    4.0 m
4    5.0 m
5    6.0 m
dtype: quantity[m]
Dtype preserved: quantity[m]

Reduction Operations#

Statistical operations that preserve units

print("\n" + "=" * 70)
print("9. Reduction Operations")
print("=" * 70)

measurements: Series = Series([10.5, 12.3, 11.8, 13.2, 10.9], dtype="quantity[mm]")

print("\nMeasurements:")
print(measurements)

# Sum
total: Series | float = measurements.sum()
print(f"\nSum: {total}")

# Mean
average: Series | float = measurements.mean()
print(f"Mean: {average}")

# Min and Max
minimum: Series | float = measurements.min()
maximum: Series | float = measurements.max()
print(f"Min: {minimum}")
print(f"Max: {maximum}")

# Standard deviation
std: Series | float = measurements.std()
print(f"Std Dev: {std}")
======================================================================
9. Reduction Operations
======================================================================

Measurements:
0    10.5 mm
1    12.3 mm
2    11.8 mm
3    13.2 mm
4    10.9 mm
dtype: quantity[mm]

Sum: 58.699999999999996 mm
Mean: 11.739999999999998 mm
Min: 10.5 mm
Max: 13.2 mm
Std Dev: 1.0830512453249845 mm

Real-World Example: Structural Analysis#

Analyzing beam deflection data

print("\n" + "=" * 70)
print("10. Real-World Example: Structural Analysis")
print("=" * 70)

# Simulated beam deflection data
analysis_df: DataFrame = DataFrame(
    {
        "beam_id": ["B001", "B002", "B003", "B004", "B005"],
        "length": Series([5.0, 10.0, 15.0, 20.0, 25.0], dtype="quantity[m]"),
        "load": Series(
            [5000.0, 10000.0, 15000.0, 20000.0, 25000.0], dtype="quantity[N]"
        ),
        "deflection": Series([2.5, 5.0, 7.5, 10.0, 12.5], dtype="quantity[mm]"),
        "stress": Series([50.0, 100.0, 150.0, 200.0, 250.0], dtype="quantity[MPa]"),
    }
)

print("\nBeam Analysis Data (SI units):")
print(analysis_df)
print("\nUnits:")
for col, unit in analysis_df.units.summary().items():
    print(f"  {col}: {unit}")

# Convert to imperial units for reporting
imperial_df: DataFrame = analysis_df.units.to(
    {"length": "ft", "load": "lbf", "deflection": "in"}
)

print("\nBeam Analysis Data (Imperial units):")
print(imperial_df)
print("\nUnits:")
for col, unit in imperial_df.units.summary().items():
    print(f"  {col}: {unit}")

# Calculate load per unit length (would need arithmetic support)
print("\nAnalysis Summary:")
print(f"  Total beams analyzed: {len(analysis_df)}")
print(f"  Average beam length: {analysis_df['length'].mean()}")
print(f"  Average load: {analysis_df['load'].mean()}")
print(f"  Average deflection: {analysis_df['deflection'].mean()}")
print(f"  Max stress: {analysis_df['stress'].max()}")
======================================================================
10. Real-World Example: Structural Analysis
======================================================================

Beam Analysis Data (SI units):
  beam_id  length       load  deflection     stress
0    B001   5.0 m   5000.0 N      2.5 mm   50.0 MPa
1    B002  10.0 m  10000.0 N      5.0 mm  100.0 MPa
2    B003  15.0 m  15000.0 N      7.5 mm  150.0 MPa
3    B004  20.0 m  20000.0 N     10.0 mm  200.0 MPa
4    B005  25.0 m  25000.0 N     12.5 mm  250.0 MPa

Units:
  length: m
  load: N
  deflection: mm
  stress: MPa

Beam Analysis Data (Imperial units):
  beam_id                 length  ...             deflection     stress
0    B001  16.404199475065617 ft  ...  0.0984251968503937 in   50.0 MPa
1    B002  32.808398950131235 ft  ...  0.1968503937007874 in  100.0 MPa
2    B003   49.21259842519686 ft  ...  0.2952755905511811 in  150.0 MPa
3    B004   65.61679790026247 ft  ...  0.3937007874015748 in  200.0 MPa
4    B005    82.0209973753281 ft  ...  0.4921259842519686 in  250.0 MPa

[5 rows x 5 columns]

Units:
  length: ft
  load: lbf
  deflection: in
  stress: MPa

Analysis Summary:
  Total beams analyzed: 5
  Average beam length: 15.0 m
  Average load: 15000.0 N
  Average deflection: 7.5 mm
  Max stress: 250.0 MPa

Accessing Underlying Quantity#

Get the full Quantity object from a Series

print("\n" + "=" * 70)
print("11. Accessing Underlying Quantity")
print("=" * 70)

temps = Series([20.0, 25.0, 30.0, 35.0], dtype="quantity[C]")
print("\nTemperature Series:")
print(temps)

# Get as Quantity object
temp_quantity: TypeAlias = temps.units.quantity
print(f"\nAs Quantity object: {temp_quantity}")
print(f"Type: {type(temp_quantity)}")
print(f"Units: {temp_quantity.units}")

# Can use Quantity methods
temp_kelvin: temp_quantity = temp_quantity.to("K")
print(f"Converted to Kelvin: {temp_kelvin}")
======================================================================
11. Accessing Underlying Quantity
======================================================================

Temperature Series:
0    20.0 C
1    25.0 C
2    30.0 C
3    35.0 C
dtype: quantity[C]

As Quantity object: [20. 25. 30. 35.] C
Type: <class 'ansys.units.quantity.Quantity'>
Units: C
Converted to Kelvin: [293.15 298.15 303.15 308.15] K

Total running time of the script: (0 minutes 0.055 seconds)

Gallery generated by Sphinx-Gallery