Skip to content

Data Manipulation

"""
Data manipulation example for aaiclick.

This example demonstrates how to use data manipulation methods:
- copy() - Create a copy of an object
- concat() - Concatenate data (creates new object)
- insert() - Insert data in place (modifies existing object)
"""

import asyncio

from aaiclick import create_object_from_value
from aaiclick.data.data_context import data_context


async def example():
    """Run all data manipulation examples."""
    # Example 1: Copying objects
    print("Example 1: Copying objects")
    print("-" * 50)

    original = await create_object_from_value([1, 2, 3])
    print(f"Original object: {original}")
    print(f"Original data: {await original.data()}")  # → [1, 2, 3]
    print(f"Original table: {original.table}\n")

    # Copy the object
    copied = await original.copy()
    print(f"Copied object: {copied}")
    print(f"Copied data: {await copied.data()}")  # → [1, 2, 3]
    print(f"Copied table: {copied.table}")
    print(f"Tables are different: {original.table != copied.table}")

    # Example 2: Concatenate - Creates new object (non-mutating)
    print("\n" + "=" * 50)
    print("Example 2: Concatenate (non-mutating)")
    print("-" * 50)

    obj_a = await create_object_from_value([1, 2, 3])
    obj_b = await create_object_from_value([4, 5, 6])

    print(f"Object A: {await obj_a.data()}")  # → [1, 2, 3]
    print(f"Object B: {await obj_b.data()}\n")  # → [4, 5, 6]

    # Concat creates a new object
    result = await obj_a.concat(obj_b)
    print(f"After concat: {await result.data()}")  # → [1, 2, 3, 4, 5, 6]
    print(f"Original A unchanged: {await obj_a.data()}")  # → [1, 2, 3]
    print(f"Original B unchanged: {await obj_b.data()}")  # → [4, 5, 6]
    print(f"Result is new object: {result.table != obj_a.table}")

    # Example 3: Concatenate with scalar value
    print("\n" + "=" * 50)
    print("Example 3: Concatenate with scalar value")
    print("-" * 50)

    obj = await create_object_from_value([10, 20, 30])
    print(f"Original array: {await obj.data()}\n")  # → [10, 20, 30]

    # Concat with scalar
    result_scalar = await obj.concat(40)
    print(f"After concat(40): {await result_scalar.data()}")  # → [10, 20, 30, 40]
    print(f"Original unchanged: {await obj.data()}")  # → [10, 20, 30]

    # Example 4: Concatenate with list value
    print("\n" + "=" * 50)
    print("Example 4: Concatenate with list value")
    print("-" * 50)

    obj = await create_object_from_value([1, 2])
    print(f"Original array: {await obj.data()}\n")  # → [1, 2]

    # Concat with list
    result_list = await obj.concat([3, 4, 5])
    print(f"After concat([3, 4, 5]): {await result_list.data()}")  # → [1, 2, 3, 4, 5]
    print(f"Original unchanged: {await obj.data()}")  # → [1, 2]

    # Example 5: Insert - Modifies in place (mutating)
    print("\n" + "=" * 50)
    print("Example 5: Insert (mutating - modifies in place)")
    print("-" * 50)

    obj_x = await create_object_from_value([100, 200, 300])
    obj_y = await create_object_from_value([400, 500, 600])

    print(f"Object X before: {await obj_x.data()}")  # → [100, 200, 300]
    print(f"Object Y: {await obj_y.data()}")  # → [400, 500, 600]
    print(f"Table X before: {obj_x.table}\n")

    # Insert modifies obj_x in place
    await obj_x.insert(obj_y)
    print(f"Object X after insert: {await obj_x.data()}")  # → [100, 200, 300, 400, 500, 600]
    print(f"Table X after (same): {obj_x.table}")
    print(f"Object Y unchanged: {await obj_y.data()}")  # → [400, 500, 600]

    # Example 6: Insert with scalar value
    print("\n" + "=" * 50)
    print("Example 6: Insert with scalar value")
    print("-" * 50)

    obj = await create_object_from_value([1, 2, 3])
    print(f"Before insert: {await obj.data()}")  # → [1, 2, 3]
    print(f"Table: {obj.table}\n")

    await obj.insert(4)
    print(f"After insert(4): {await obj.data()}")  # → [1, 2, 3, 4]
    print(f"Table (same): {obj.table}")

    # Example 7: Insert with list value
    print("\n" + "=" * 50)
    print("Example 7: Insert with list value")
    print("-" * 50)

    obj = await create_object_from_value([10, 20])
    print(f"Before insert: {await obj.data()}")  # → [10, 20]
    print(f"Table: {obj.table}\n")

    await obj.insert([30, 40, 50])
    print(f"After insert([30, 40, 50]): {await obj.data()}")  # → [10, 20, 30, 40, 50]
    print(f"Table (same): {obj.table}")

    # Example 8: Multiple inserts
    print("\n" + "=" * 50)
    print("Example 8: Multiple consecutive inserts")
    print("-" * 50)

    obj = await create_object_from_value([1, 2])
    print(f"Initial: {await obj.data()}")  # → [1, 2]

    await obj.insert(3)
    print(f"After insert(3): {await obj.data()}")  # → [1, 2, 3]

    await obj.insert([4, 5])
    print(f"After insert([4, 5]): {await obj.data()}")  # → [1, 2, 3, 4, 5]

    await obj.insert(6)
    print(f"After insert(6): {await obj.data()}")  # → [1, 2, 3, 4, 5, 6]

    # Example 9: Comparing concat vs insert
    print("\n" + "=" * 50)
    print("Example 9: Comparing concat vs insert")
    print("-" * 50)

    # Using concat (non-mutating)
    a1 = await create_object_from_value([1, 2, 3])
    print("concat approach:")
    print(f"  Original a1: {await a1.data()}, table: {a1.table}")
    a1_new = await a1.concat([4, 5])
    print(f"  After concat: {await a1_new.data()}, table: {a1_new.table}")  # → [1, 2, 3, 4, 5]
    print(f"  Original a1 unchanged: {await a1.data()}, table: {a1.table}\n")  # → [1, 2, 3]

    # Using insert (mutating)
    a2 = await create_object_from_value([1, 2, 3])
    print("insert approach:")
    print(f"  Original a2: {await a2.data()}, table: {a2.table}")
    await a2.insert([4, 5])
    print(f"  After insert: {await a2.data()}, table: {a2.table} (same)")  # → [1, 2, 3, 4, 5]

    # Example 10: Real-world scenario - Building a dataset
    print("\n" + "=" * 50)
    print("Example 10: Real-world scenario - Building a dataset")
    print("-" * 50)

    # Start with initial data
    dataset = await create_object_from_value([10.5, 20.3, 30.7])
    print(f"Initial dataset: {await dataset.data()}")  # → [10.5, 20.3, 30.7]

    # Add more measurements in place
    await dataset.insert([15.2, 25.8])
    print(f"Added batch 1: {await dataset.data()}")  # → [10.5, 20.3, 30.7, 15.2, 25.8]

    await dataset.insert(35.9)
    print(f"Added single value: {await dataset.data()}")  # → [10.5, 20.3, 30.7, 15.2, 25.8, 35.9]

    await dataset.insert([12.1, 22.4, 32.6])
    print(f"Added batch 2: {await dataset.data()}")  # → [10.5, 20.3, 30.7, 15.2, 25.8, 35.9, 12.1, 22.4, 32.6]

    # Calculate statistics on final dataset (returns Objects, use .data() to extract values)
    print("\nFinal dataset statistics:")
    print(f"  Count: {len(await dataset.data())}")  # → 9
    print(f"  Min: {await dataset.min().data():.2f}")  # → 10.50
    print(f"  Max: {await dataset.max().data():.2f}")  # → 35.90
    print(f"  Mean: {await dataset.mean().data():.2f}")  # → 22.83
    print(f"  Std: {await dataset.std().data():.2f}")  # → 8.62

    # Note: All objects created via context are automatically cleaned up when context exits
    print("\n" + "=" * 50)
    print("Cleanup: All context-created objects will be cleaned up automatically")
    print("-" * 50)


async def amain():
    """Main entry point that creates data_context() and calls example."""
    async with data_context():
        await example()


if __name__ == "__main__":
    print("=" * 50)
    print("aaiclick Data Manipulation Example")
    print("=" * 50)
    print("\nNote: This example requires a running ClickHouse server")
    print("      on localhost:8123\n")
    asyncio.run(amain())