How to use numpy arrays with fractions?
Asked Answered
G

2

13

I'm trying to implement the simplex method in Python so I need to use the Gaussian elimination on arrays. Very often fractions come up and for more clarity and precision I would like to keep the fractional form instead of using floats. I know the 'fractions' module but I'm struggling to use it. I wrote my code using this module but the arrays are always returned with floats. Isn't it possible to print an array with fractions inside ? On this basic example :

>>> A
array([[-1.,  1.],
       [-2., -1.]])
>>> A[0][0] = Fraction(2, 3)
>>> A
array([[ 0.66666667,  1. ],
       [-2.        , -1. ]])

I would like to have

array([[2/3,    1. ],
       [-2.,   -1. ]])

It seems numpy always switches to floats

Glyptic answered 3/3, 2017 at 11:34 Comment(3)
If you want to work with matrices of exact rational numbers, sympy would probably serve you better.Integrand
Thank you for your answer but I won't use sympy since I already started my code with numpy. I didn't know sympy so I keep that in mind for a next code !Glyptic
I tested sympy on matrices and it's very very slow: #45797247Geophilous
D
11

You can also convert the entire array to an object array of Fraction objects, by abusing the element-wise conversion of numpy arrays under arithmetic operations. (Note: this requires the original array to be an integer array, since arithmetic between floats and Fractions produce floats.)

>>> A = np.array([[-1,  1],[-2, -1]])
array([[-1,  1],
       [-2, -1]])
>>>
>>> A.dtype
dtype('int64')
>>>
>>> A = A + Fraction()
>>> A
array([[Fraction(-1, 1), Fraction(1, 1)],
       [Fraction(-2, 1), Fraction(-1, 1)]], dtype=object)

With the array in this format, any further arithmetic performed will be over elements of type Fraction.

Edit: disclaimers

As mentioned by @Hi-Angel in the comments, there are a number of NumPy/SciPy functions (e.g., np.linalg.inv) that expect input arrays to use a primitive dtype (e.g., int32, float64, etc.); these functions tend to be C/Cython-optimized routines that only work on C-primitives. And because fractions.Fraction is a Python object, these functions will not work on arrays of Fractions.

And as mentioned elsewhere, even the functions that do work on Fraction arrays will run notably slower on them, compared to running on NumPy arrays of primitive dtypes.

However, if you just need a custom numeric object for your application, like the arbitrary-precision rational type Fraction or the base-10 floating-point type decimal.Decimal, and want the convenience of e.g. element-wise operations on arrays, you CAN use NumPy arrays to achieve that using the method above or similar methods.

But it's not as fast or well-supported as using arrays of primitives, so personally if I don't NEED a custom number type I just use float64s or int64s.

Dottydoty answered 29/3, 2018 at 0:40 Comment(9)
Is there no other way to do it? I tried using .astype but it failed. Can we not cast the array directly somehow? This hack is great and it works.Pest
@Param I assume you mean that you tried A.astype(Fraction)? If so, then you're right that does NOT work, because numpy has no dtype that is comparable to python's built-in Fraction type.Dottydoty
a more explicit method, if you need one, could be to do something like np.vectorize(Fraction)(A): this will call Fraction on each element of the array and return the result. This also automatically converts the result array to an object-typed array. (However I hear that np.vectorize is not always especially performant...)Dottydoty
Well, I mean, it does seem to work as in, it stores the array as fractions. But it looks useless, because I can't do anything with it: e.g. when I try to calculate the inverse in fractions, I get: np.linalg.inv(A)numpy.core._exceptions._UFuncInputCastingError: Cannot cast ufunc 'inv' input from dtype('O') to dtype('float64') with casting rule 'same_kind'Loquat
@Loquat that’s good to note for sure: the fraction number type (and Python objects in general) does not have first-class support in numpy. so some numpy functions that expect an array of numpy primitives may failDottydoty
On the other hand, I’ve written a lot of numpy code that doesn’t use np.linalg.inv. So personally I think saying it’s “useless”, especially if in reference to a general use case, is a little strong.Dottydoty
@Dottydoty so you're saying some functions work and some doesn't? That's good to know, thanks! That means one still could use numpy with fractions, just reimplement those functions that doesn't work. I think, it might be useful to note that in the answer, to save some potential confusion.Loquat
@Loquat I just added that section; does it make sense and explain what you were asking?Dottydoty
Yeah, thanks, that clears that up. ⁺¹ from meLoquat
E
7

Since Fractions are not a native NumPy dtype, to store a Fraction in a NumPy array you need to convert the array to object dtype:

import numpy as np
from fractions import Fraction

A = np.array([[-1.,  1.],
              [-2., -1.]])   # <-- creates an array with a floating-point dtype (float32 or float64 depending on your OS)
A = A.astype('object')
A[0, 0] = Fraction(2,3)
print(A)

prints

[[Fraction(2, 3) 1.0]
 [-2.0 -1.0]]

PS. As user2357112 suggests, you might be better off using sympy if you wish to use rational numbers. Or, just represent the matrix as a list of lists. There are no speed advantages to using NumPy if your arrays are of object dtype.

import sympy as sy

A = [[-1.,  1.],
     [-2., -1.]]
A[0][0] = sy.Rational('2/3')
print(A)

prints

[[2/3, 1.0], [-2.0, -1.0]]
Energid answered 3/3, 2017 at 11:41 Comment(1)
Thank you for your answer, converting the array is exactly what I needed.Glyptic

© 2022 - 2024 — McMap. All rights reserved.