Marrow provides SIMD-vectorized compute kernels for arithmetic, comparisons, aggregation, selection, and strings. All kernels are null-aware: the exact null handling behaviour depends on the operation.
Arithmetic
Element-wise binary operations: add, sub, mul, div.
a = ma.array([1 , 2 , 3 , 4 , 5 ], type = ma.int64())
b = ma.array([10 , 20 , 30 , 40 , 50 ], type = ma.int64())
print ("add:" , ma.add(a, b, None ))
print ("sub:" , ma.sub(b, a, None ))
print ("mul:" , ma.mul(a, b, None ))
print ("div:" , ma.div(b, a, None ))
add: PrimitiveArray[int64]([11, 22, 33, 44, 55])
sub: PrimitiveArray[int64]([9, 18, 27, 36, 45])
mul: PrimitiveArray[int64]([10, 40, 90, 160, 250])
div: PrimitiveArray[int64]([10, 10, 10, 10, 10])
Floats work the same way:
x = ma.array([1.0 , 2.0 , 3.0 ])
y = ma.array([0.5 , 1.5 , 2.5 ])
print ("add:" , ma.add(x, y, None ))
print ("div:" , ma.div(x, y, None ))
add: PrimitiveArray[float64]([1.5, 3.5, 5.5])
div: PrimitiveArray[float64]([2.0, 1.3333333333333333, 1.2])
Null propagation
If either operand at a position is null, the result at that position is null. This mirrors SQL’s three-valued logic.
a = ma.array([1 , None , 3 , None ])
b = ma.array([10 , 20 , 30 , 40 ])
result = ma.add(a, b, None )
print (result) # index 1 and 3 are null
print ("null count:" , result.null_count())
PrimitiveArray[int64]([11, NULL, 33, NULL])
null count: 2
# Both inputs can contribute nulls
a = ma.array([None , 2 , 3 , None ])
b = ma.array([10 , None , 30 , None ])
print (ma.add(a, b, None )) # null at 0, 1, and 3
PrimitiveArray[int64]([NULL, NULL, 33, NULL])
Aggregates
Aggregates reduce an array to a single scalar. They skip null values by default — an all-null or empty array returns the identity value.
Numeric aggregates
arr = ma.array([1 , None , 3 , None , 5 ])
print ("sum: " , ma.sum_(arr, None )) # 1+3+5 = 9
print ("product:" , ma.product(arr, None )) # 1*3*5 = 15
print ("min: " , ma.min_(arr, None ))
print ("max: " , ma.max_(arr, None ))
sum: 9
product: 15
min: 1
max: 5
Aggregates always return float64 regardless of input type:
ints = ma.array([10 , 20 , 30 ], type = ma.int32())
result = ma.sum_(ints, None )
print (result, type (result))
Boolean aggregates
any_ and all_ operate on boolean arrays. Nulls are skipped.
flags = ma.array([True , False , None , True ])
print ("any_:" , ma.any_(flags)) # True — at least one True
print ("all_:" , ma.all_(flags)) # False — False is present
all_true = ma.array([True , True , None ])
print ("all_ with nulls:" , ma.all_(all_true)) # True — only True values
all_false = ma.array([None , None ], type = ma.bool_())
print ("all_ empty/null:" , ma.all_(all_false)) # True (identity)
print ("any_ empty/null:" , ma.any_(all_false)) # False (identity)
all_ with nulls: True
all_ empty/null: True
any_ empty/null: False
Selection
filter_
filter_(array, mask) keeps elements where the boolean mask is True. The mask must be a BoolArray of the same length.
arr = ma.array([10 , 20 , 30 , 40 , 50 ])
mask = ma.array([True , False , True , False , True ])
print (ma.filter_(arr, mask)) # [10, 30, 50]
PrimitiveArray[int64]([10, 30, 50])
Nulls in the source array are preserved through the filter:
arr = ma.array([1 , None , 3 , 4 , None ])
mask = ma.array([True , True , False , True , True ])
print (ma.filter_(arr, mask)) # [1, NULL, 4, NULL]
PrimitiveArray[int64]([1, NULL, 4, NULL])
Nulls in the mask are treated as False (the element is excluded):
arr = ma.array([1 , 2 , 3 , 4 ])
mask = ma.array([True , None , True , None ])
print (ma.filter_(arr, mask)) # [1, 3]
PrimitiveArray[int64]([1, 3])
drop_nulls
drop_nulls(array) removes all null positions. Equivalent to filter_(array, validity_bitmap).
arr = ma.array([1 , None , 3 , None , 5 ])
print (ma.drop_nulls(arr)) # [1, 3, 5]
PrimitiveArray[int64]([1, 3, 5])
Works on numeric array types:
f = ma.array([1.0 , None , 3.0 , None , 5.0 ])
print (ma.drop_nulls(f))
PrimitiveArray[float64]([1.0, 3.0, 5.0])
Comparisons
Element-wise comparison kernels return a boolean array. Nulls propagate: if either input at a position is null, the output is null.
a = ma.array([1 , 2 , 3 , None , 5 ])
b = ma.array([1 , 3 , 2 , 4 , 5 ])
print ("equal: " , ma.equal(a, b, None ))
print ("not_equal: " , ma.not_equal(a, b, None ))
print ("less: " , ma.less(a, b, None ))
print ("less_equal: " , ma.less_equal(a, b, None ))
print ("greater: " , ma.greater(a, b, None ))
print ("greater_equal:" , ma.greater_equal(a, b, None ))
equal: BoolArray([True, False, False, NULL, True])
not_equal: BoolArray([False, True, True, NULL, False])
less: BoolArray([False, True, False, NULL, False])
less_equal: BoolArray([True, True, False, NULL, True])
greater: BoolArray([False, False, True, NULL, False])
greater_equal: BoolArray([True, False, True, NULL, True])
The null at index 3 propagates to all output arrays:
result = ma.equal(a, b, None )
print ("null count:" , result.null_count()) # 1
print ("is_valid(3):" , result.is_valid(3 )) # False
null count: 1
is_valid(3): False
Null behaviour summary
add, sub, mul, div
either operand null
null propagates
equal, less, greater, …
either operand null
null propagates
sum_, product, min_, max_
element null
element skipped
any_, all_
element null
element skipped
filter_ (source null)
source element null
null preserved in output
filter_ (mask null)
mask element null
element excluded
drop_nulls
element null
element removed