When a function which has one parameter receives np.array - python

Suppose there is a function:
def test(x):
return x**2
When I give a list of ints to the function, an error is raised:
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'
But an array of ints instead, the function returns an array of outputs.
How is this possible?

It's important to understand that operators are functions too:
Writing a**b is like writing pow(a, b)
Functions can't guess what the expected behavior is when you give them different inputs, so behind the scenes, pow(a, b) has different implementations for different inputs (i.e. for two integers, return the first in the power of the second. For an array of integers, return an array where each cell has the corresponding cell in the input array in the power of the second integer)
whoever implemented the numpy array created a ** implementation for it, but an ordinary list doesn't have a ** implementation.
If you want to raise a list to a power, use list comprehension:
[xi ** 2 for xi in x]
You can also write your own class and implement ** for it.

I don't see why it would be impossible. Although at a very high level Python lists and Numpy arrays may appear to be the same (i.e. a sequence of numbers), they are implemented in different ways. Numpy is particularly known for its array operations (where an operation can be applied to each of an array's elements in one go).
Here's another example where you can see their differences in action:
a = [1, 2, 3, 4, 5]
print(np.array(a) * 5)
print(a * 5)

You can use this instead for lists:
x = [k**2 for k in x]
return x
The function you wrote works fine for Numpy array but not for lists.
Use the above line to avoid that error.

Related

Is there a way to get every element of a list without using loops?

I found this task in a book of my prof:
def f(x):
return f = log(exp(z))
def problem(M: List)
return np.array([f(x) for x in M])
How do I implement a solution?
Numpy is all about performing operations on entire arrays. Your professor is expecting you to use that functionality.
Start by converting your list M into array z:
z = np.array(M)
Now you can do elementwise operations like exp and log:
e = np.exp(z)
f = 1 + e
g = np.log(f)
The functions np.exp and np.log are applied to each element of an array. If the input is not an array, it will be converted into one.
Operations like 1 + e work on an entire array as well, in this case using the magic of broadcasting. Since 1 is a scalar, it can unambiguously expanded to the same shape as e, and added as if by np.add.
Normally, the sequence of operations can be compactified into a single line, similarly to what you did in your initial attempt. You can reduce the number of operations slightly by using np.log1p:
def f(x):
return np.log1p(np.exp(x))
Notice that I did not convert x to an array first since np.exp will do that for you.
A fundamental problem with this naive approach is that np.exp will overflow for values that we would expect to get reasonable results. This can be solved using the technique in this answer:
def f(x):
return np.log1p(np.exp(-np.abs(x))) + np.maximum(x, 0)

How do I subract results from different functions from each other in Python?

I have a function that I've written out and would like to perform mathematical operations on it, the same way I can with numbers.
For instance, with the code below, I would like to take Sup(3) - Sup(2) = result, but this doesn't work. Can I take functions that I've defined and perform mathematical operations on them, the same way we can perform mathematical operations on numbers (i.g, 2 * 2 = 4)?
For n = 2, my result is 1.083
For n = 3, my result is 1.717 using the code below.
def Sup(n):
mylist = []
for n in range (2,2**n+1):
Su = (1/n)
mylist.append(Su)
#print(mylist)
print (sum(mylist))
When I attempt this operation, I get the following error:
---> 12 Sup(2)- Sup(3)
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'neType'
What does this mean?
Can I take functions that I've defined and perform mathematical operations on them, the same way we can perform mathematical operations on numbers?
Yes you can, assuming that your functions return numbers.
What does this mean?
As pointed out in comments, it means that your function doesn´t return anything. Adding return to your function should do the trick:
def Sup(n):
mylist = []
for n in range (2,2**n+1):
Su = (1/n)
mylist.append(Su)
#print(mylist)
print (sum(mylist))
return sum(mylist)

Numerical Python - how do I make this a ufunc?

new to NumPy and may not be searching properly, so I'll take the lumps if this is a common question...
I'm working on a problem where I need to calculate log(n!) for relatively large numbers - ie. to large to calculate the factorial first, so I've written the following function:
def log_fact(n):
x = 0
for i in range(1,n+1):
x += log(i)
return x
Now the problem is that I want to use this as part of the function passed to curve_fit:
def logfactfunc(x, a, b, c):
return a*log_fact(x) + b*x + c
from scipy.optimize import curve_fit
curve_fit(logfactfunc, x, y)
However, this produces the following error:
File "./fit2.py", line 16, in log_fact
for i in range(1,n+1):
TypeError: only length-1 arrays can be converted to Python scalars
A little searching suggested numpy.frompyfunc() to convert this to a ufunc
curve_fit(np.frompyfunc(logfactfunc, 1, 1), data[k].step, data[k].sieve)
TypeError: <ufunc 'logfactfunc (vectorized)'> is not a Python function
Tried this as well:
def logfactfunc(x, a, b, c):
return a*np.frompyfunc(log_fact, 1, 1)(x) + b*x + c
File "./fit2.py", line 30, in logfactfunc
return a*np.frompyfunc(log_fact, 1, 1)(x) + b*x + c
TypeError: unsupported operand type(s) for +: 'numpy.ndarray' and 'numpy.float64
Any ideas on how I can get my log_fact() function to be used within the curve_fit() function??
Thanks!
Your log_fact function is closely related to the gammaln function, which is defined as a ufunc in scipy.special. Specifically, log_fact(n) == scipy.special.gammaln(n+1). For even modest values of n, this is significantly faster:
In [15]: %timeit log_fact(19)
10000 loops, best of 3: 24.4 us per loop
In [16]: %timeit scipy.special.gammaln(20)
1000000 loops, best of 3: 1.13 us per loop
Also, the running time of gammaln is independent of n, unlike log_fact.
Your log_fact function is being called with arrays as input parameters, which is what's throwing off your method. A possible way of vectorizing your code would be the following:
def log_fact(n):
n = np.asarray(n)
m = np.max(n)
return np.take(np.cumsum(np.log(np.arange(1, m+1))), n-1)
Taking it for a test ride:
>>> log_fact(3)
1.791759469228055
>>> log_fact([10, 15, 23])
array([ 15.10441257, 27.89927138, 51.60667557])
>>> log_fact([[10, 15, 23], [14, 15, 8]])
array([[ 15.10441257, 27.89927138, 51.60667557],
[ 25.19122118, 27.89927138, 10.6046029 ]])
The only caveat with this approach is that it stores an array as long as the largest value you call it with. If your n gets into the billions, you'll probably break it. Other than that, it actually avoids repeated calculations if you call it with many values.
If n really is large (say larger than about 10 or so) then a much better approach is to using Stirling's approximation. This will be much more efficient. It will also be easy to vectorize.
For the approach you are taking your log_fact(n) function can be written much more efficiently and compactly as
def log_fact(n) :
return np.sum(np.log(np.arange(1,n+1)))
This does not help with your problem though. We could vectorize this as #Isaac shows or just use np.vectorize() which is a convenience wrapper that does basically the same thing. Note that it does not offer speed advantages, you are still using Python loops which are slow.
That being said, use Stirling's approximation!
As far as I can tell creating a ufunc is fairly involved, and my require writing your function in c. See here for the documentation on creating ufuncs.
You might instead consider just writing a version of your function that takes and returns ndarrays. For instance:
def logfact_arr(a):
return np.array([log_fact(x) for x in a.flat]).reshape(a.shape)
The previous answers show efficient ways to solve your problem. But the precise answer to your question, i.e., how to vectorize log_fact function is to use np.vectorize:
vlog_fact=np.vectorize(log_fact)
def vlogfactfunc(x, a, b, c):
return a*vlog_fact(x) + b*x + c
With that, you can call curve_fit(vlogfactfunc, np.array([1,2,3]), np.array([ -1. , 4.465 , 11.958]))
As you suggested, you could also use np.frompyfunc but as you can read in its documentation, that always returns python objects, as then curve_fit complains:
TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'
A workaround is to covert the returned array to an array of floats:
ulog_fact = np.frompyfunc(log_fact, 1,1)
def ulogfactfunc(x, a, b, c):
return a*ulog_fact(x).astype(np.float) + b*x + c
So you can also call curve_fit with ulogfactfunc
Hope this helps!

A function powers(n,k) that returns the list [1,n,n^2,n^3,...,n^k] where k is an integer

def powers(n, k):
"""Compute and returns the indices numbers of n, up to and including n^k"""
b = range(k+1)
print b
a = []
for i in b:
print a
a.append(n**b)
return a
The above code is my attempt at the problem. However it returns:
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'list'
So there is some problem with the n**b part of my code.
You might be interested in using list comprehension, these are usually more eficient than looping through a list yourself. Also, you we're using the list your were iterating over instead of the item.
def powers(n, k):
"""Compute and returns the indices numbers of n, up to and including n^k"""
return [n**i for i in range(k+1)]
Instead of
a.append(n**b)
use
a.append(n**i)
Or you can simply use the map() function:
base = 10
lst = xrange(10)
result = map(lambda x: base**x, lst) # 10^0 to 10^9
If you're not working with floating point arithmetics (or you don't care about imprecisions introduced by rounding), you could also use incremental approach (n^k = n^(k-1) * n), which could be a bit faster for large arrays (while the algorithms above usually compute in n log n, this one would be linear).

"'generator' object is not subscriptable" error

Why am I getting this error, from line 5 of my code, when attempting to solve Project Euler Problem 11?
for x in matrix:
p = 0
for y in x:
if p < 17:
currentProduct = int(y) * int(x[p + 1]) * int(x[p + 2]) * int(x[p + 3])
if currentProduct > highestProduct:
print(currentProduct)
highestProduct = currentProduct
else:
break
p += 1
'generator' object is not subscriptable
Your x value is is a generator object, which is an Iterator: it generates values in order, as they are requested by a for loop or by calling next(x).
You are trying to access it as though it were a list or other Sequence type, which let you access arbitrary elements by index as x[p + 1].
If you want to look up values from your generator's output by index, you may want to convert it to a list:
x = list(x)
This solves your problem, and is suitable in most cases. However, this requires generating and saving all of the values at once, so it can fail if you're dealing with an extremely long or infinite list of values, or the values are extremely large.
If you just needed a single value from the generator, you could instead use itertools.islice(x, p) to discard the first p values, then next(...) to take the one you need. This eliminate the need to hold multiple items in memory or compute values beyond the one you're looking for.
import itertools
result = next(itertools.islice(x, p))
As an extension to Jeremy's answer some thoughts about the design of your code:
Looking at your algorithm, it appears that you do not actually need truly random access to the values produced by the generator: At any point in time you only need to keep four consecutive values (three, with an extra bit of optimization). This is a bit obscured in your code because you mix indexing and iteration: If indexing would work (which it doesn't), your y could be written as x[p + 0].
For such algorithms, you can apply kind of a "sliding window" technique, demonstrated below in a stripped-down version of your code:
import itertools, functools, operator
vs = [int(v) for v in itertools.islice(x, 3)]
for v in x:
vs.append(int(v))
currentProduct = functools.reduce(operator.mul, vs, 1)
print(currentProduct)
vs = vs[1:]

Categories