NumJy

NumJy is a limited implementation of NumPy, the numeric package for Python, for Jython, i.e. the Java-based version of Python. It allows Jython code to handle multi-dimensional arrays in a syntax similar to NumPy.

NumJy Basics

Import

To access NumJy, import it similar to NumPy, but using 'numjy' instead of 'numpy':

from numjy import *

When running unter Python, this will actually import the original NumPy. Under Jython, it imports the NumJy packages that emulate a subset of NumPy.

Data Types

TypeDescription
float, float6464-bit floating point
float3232-bit floating point
int, int6464-bit integer
int3232-bit integer
int1616-bit integer
int88-bit integer
boolBoolean (True, False), stored as 8-bit integer

These serve as type identifiers when selecting a particular data type, for example:

a = linspace(1, 10, 20, dtype=float32)

Creating Arrays

You can convert Python arrays or lists into NumJy arrays with the array command:

array([ 1, 2, 3, 4])
array([ 1.0, 2.0, 3.0, 4.0 ])
array([ [ 1, 2], [3, 4] ])

There are commands to create specific arrays:

>>> zeros( (2, 3) )
array([
  [ 0.0, 0.0, 0.0 ],
  [ 0.0, 0.0, 0.0 ]
])
>>> ones(( 3,2))
array([
  [ 1.0, 1.0 ],
  [ 1.0, 1.0 ],
  [ 1.0, 1.0 ]
])
>>> linspace(2, 10, 5)
array([ 2.0, 4.0, 6.0, 8.0, 10.0 ])
>>> arange(1, 5, 0.5)
array([ 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5 ])
>>> arange(5, 1, -2)
array([ 5.0, 3.0 ])

For details, refer to their help:

>>> help(array)
>>> help(arange)
>>> help(linspace)

Reshaping Arrays

All arrays are internally stored as a flat list, but they are presented in a multi-dimensional shape.

Arrays can be viewed in a different shape as long as their overall size remains the same:

>>> # 1-D array
>>> arange(6)
array([ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
>>> arange(6).shape(6,)

>>> # Change shape to 2x3
>>> arange(6).reshape(2, 3)
array([
  [ 0.0, 1.0, 2.0 ],
  [ 3.0, 4.0, 5.0 ]
])
>>> arange(6).reshape(2,3).shape(2, 3)

>>> # Change shape to 3x2
>>> arange(6).reshape(3, 2)
array([
  [ 0.0, 1.0 ],
  [ 2.0, 3.0 ],
  [ 4.0, 5.0 ]
])

>>> # Error when trying to change the overall size
>>> arange(6).reshape(7)
...IllegalArgumentException: Cannot change shape from [6] to [7]

The number of dimensions is also known as 'rank' of the array. A basic 1-dimensional array has rank 1. Re-shaped to 2x3 or 3x2 it has a rank of 2:

>>> arange(6).ndim
1
>>> arange(6).reshape(3, 2).ndim
2

Indexing

NumJy array elements are accessed just like other Python arrays via square brackets, using as many index elements as required by the dimensions of the array:

>>> arange(6)[0]
0.0
>>> arange(6)[5]
5.0

>>> arange(6)[-1]
5.0

>>> arange(6).reshape(2,3)[1,1]
4.0

Note how negative indices reference elements from the 'end' of their respective dimension. For the example with 6 elements, numbered 0 to 5, the index -1 references the same element as the index 5.

Views

Operations like reshape(), transpose() or the slicing described in the following section create a new view of the original array. To preserve memory as well as to allow certain operations on the data, the underlying data is not copied!

Example: Changing the reshaped view 'b' of 'a' also affects corresponding elements in 'a':

>>> a=arange(6)
>>> b=a.reshape(2, 3)
>>> b
array([
  [ 0.0, 1.0, 2.0 ],
  [ 3.0, 4.0, 5.0 ]
])
>>> b[1,2]=666
>>> b
array([
  [ 0.0, 1.0, 2.0 ],
  [ 3.0, 4.0, 666.0 ]
])
>>> a
array([ 0.0, 1.0, 2.0, 3.0, 4.0, 666.0 ])

The original array is available as the 'base':

>>> b = arange(6).reshape(2, 3)
>>> b
array([
  [ 0.0, 1.0, 2.0 ],
  [ 3.0, 4.0, 5.0 ]
])
>>> b.base
array([ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])

If necessary, it is possible to create a copy which will then no longer be assiciated with any 'base':

>>> b = copy(a.reshape(2, 3))
>>> b = a.reshape(2, 3).copy()
>>> b.base is None
True

Slicing

NumJy supports the basic slicing operations of NumPy. When accessing a 1-dimensional array, the result is a sub-section of the original array:

>>> arange(6)
array([ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])

>>> # Use start:stop slice
>>> arange(6)[2:4]
array([ 2.0, 3.0 ])

>>> # Use start:stop:step slice
>>> arange(6)[1:5:2]
array([ 1.0, 3.0 ])

>>> # start:...
>>> arange(6)[1:]
array([ 1.0, 2.0, 3.0, 4.0, 5.0 ])

>>> # ...stop
>>> arange(6)[:3]
array([ 0.0, 1.0, 2.0 ])

>>> # Specify stop relative to length
>>> arange(6)[:-1]
array([ 0.0, 1.0, 2.0, 3.0, 4.0 ])

>>> # Specify step
>>> arange(6)[::2]
array([ 0.0, 2.0, 4.0 ])

The result is a view of the original array. Changing the slice will also update the original array:

>>> orig = arange(6)
>>> sub = orig[2:4]
>>> sub[1] = 666
>>> sub
array([ 2.0, 666.0 ])
>>> orig
array([ 0.0, 1.0, 2.0, 666.0, 4.0, 5.0 ])

When accessing multi-dimensional arrays, use one start:stop:step slice per dimension. When fewer indices are provided, the missing indices default to the complete axis:

>>> orig=arange(6).reshape(2, 3)
>>> orig
array([
  [ 0.0, 1.0, 2.0 ],
  [ 3.0, 4.0, 5.0 ]
])
>>> # Using only [1] is the same as [1, :]
>>> row1 = orig[1]
>>> row1
array([ 3.0, 4.0, 5.0 ])
>>> # Access column
>>> col2 = orig[:, 2]
>>> col2
array([ 2.0, 5.0 ])

Note how the col2 in the above example is not a strict column of shape 2x1. Instead, it is a 1-D array with 2 elements. In the example, the index 2 was used to select just one element of the second axis. When using a plain index to access a individual element on an axis, that axis is 'collapsed' and removed from the result.

To force NumPy/NumJy to keep that single-element axis, specify a complete slice, even if the slice addresses only a single element:

>>> orig[:, 2:3:1]
array([
  [ 2.0 ],
  [ 5.0 ]
])

Since slicing creates a view into the original array, it is possible to assign data to N-dimensional slices, but the array shapes must match:

>>> # Create 2x3  array
>>> orig = arange(6).reshape(2, 3)
>>> # In all rows, update every other column with a 2x2 data
>>> orig[:, ::2] = array([ [ 40, 42], [ 43, 45 ]])
>>> orig
array([
  [ 40.0, 1.0, 42.0 ],
  [ 43.0, 4.0, 45.0 ]
])

Math Support

Broadcasting

Math operations in NumJy and NumPy are fundamentally element-by-element. When for example adding two vectors, their elements are added:

>>> arange(6)
array([ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
>>> arange(6) + arange(6)
array([ 0.0, 2.0, 4.0, 6.0, 8.0, 10.0 ])

It is also possible to add scalars to all elements of an array:

>>> arange(6) + 10
array([ 10.0, 11.0, 12.0, 13.0, 14.0, 15.0 ])

When operating on two NumJy/NumPy arrays, their shapes must be compatible according to the broadcasting rule:

The size of the trailing axes for both arrays in an operation must either be the same, or one of them must be one.

For example, these arrays are all compatible to the shape 10x5x3:

The resulting array will combine all dimensions as in the following example, which could represent a 10x5 image where each pixel in turn has 3 RGB values:

Image dimension  : 10 x 5 x 3
Operand dimension:          3
Result           : 10 x 5 x 3

The elements of the smaller array are applied to the trailing dimensions of the bigger array. Dimensions of size 1 are 'broadcast' over all element of the corresponding dimension in the other array.

Examples:

>>> # Apply array of shape ( 1 ) to all elements of ( 2, 2 )
>>> array([
...   [ 1.0, 2.0 ],
...   [ 3.0, 4.0 ] ] ) + array( [ 10 ] )
array([
  [ 11.0, 12.0 ],
  [ 13.0, 14.0 ]
])

>>> # Apply 'column' array of shape ( 2, 1 ) to columns of ( 2, 2 )
>>> array([
...   [ 1.0, 2.0 ],
...   [ 3.0, 4.0 ] ] ) + array( [ [ 10 ], [ 20 ] ])
array([
  [ 11.0, 12.0 ],
  [ 23.0, 24.0 ]
])

For a longer explanation, see http://www.scipy.org/EricsBroadcastingDoc

Operators

Basic add, substract, ... operation between arrays, returning an array with 'broadcast' shape:

OperatorDescription
-aCompute element-wise negative
a + bAdd elements of arrays a, b
a - bSubtract ...
a * bMultiply ...
a / bDivide ... Division by 0 results in 0, NaN, Infinity or big numbers, depending on the data type.
a ** bElement-wise exponentiation, raise a to b's power
a += bIncrement elements of array a by elements of b
a -= bSubtract ...
a *= bMultiply ...
a /= bDivide ...
a **= bRaise to b's power
a > bTrue/False if corresponding array elements are greater than, ...
a >= b... greater or equal ...
a < b... less than ...
a <= b... greater or equal ...
a == b... equal ...
a != b... not equal ...

Functions

Functions that operate on each array element, returning an array of same shape as their input:

OperatorDescription
abs(a)Compute element-wise absolute values
exp(a)... exponential ...
log(a)... logarithm ...
log10(a)... logarithm (base 10) ...
sqrt(a)... square root ...

Functions that operate on two arrays, returning an array with 'broadcast' shape:

OperatorDescription
pow(a, b)Same as a ** b

Functions that return a scalar:

OperatorDescription
any(a)Return True if any element is non-zero
all(a)Return True if all elements are non-zero
nonzero(a)Return indices of array elements that are non-zero
min(a)Minimum element
max(a)Maximum element
sum(a)Sum over all elements

Matrix Operations

While the normal array multiplication is element-by-element as described before and not in the matrix sense, there are matrix type operations. They are limited to arrays of certain shapes.

>>> # 2-D matrix, 1-D vector
>>> mat = array( [ [ 1, 2 ], [ 3, 4 ] ])
>>> vec = array( [ 10, 20 ] )

>>> # Transpose a matrix
>>> transpose(mat)
array([
  [ 1.0, 3.0 ],
  [ 2.0, 4.0 ]
])

>>> # .. shortcut
>>> mat.T
array([
  [ 1.0, 3.0 ],
  [ 2.0, 4.0 ]
])

>>> # Matrix 'dot' product
>>> dot(mat, vec)
array([ 50.0, 110.0 ])

>>> dot(mat, mat)
array([
  [ 7.0, 10.0 ],
  [ 15.0, 22.0 ]
])

Example for computing rotation:

from math import cos, sin, radians

angle = radians(90)

rotate = array( [ [ cos(angle), -sin(angle) ], [ sin(angle), cos(angle) ] ] )
vec = array( [ 1, 0 ])

# Results in [ 0, 1 ]
result = dot(rotate, vec)