Geometric progression using Python / Pandas / Numpy (without loop and using recurrence) - python

I'd like to implement a geometric progression using Python / Pandas / Numpy.
Here is what I did:
N = 10
n0 = 0
n_array = np.arange(n0, n0 + N, 1)
u = pd.Series(index = n_array)
un0 = 1
u[n0] = un0
for n in u.index[1::]:
#u[n] = u[n-1] + 1.2 # arithmetic progression
u[n] = u[n-1] * 1.2 # geometric progression
print(u)
I get:
0 1.000000
1 1.200000
2 1.440000
3 1.728000
4 2.073600
5 2.488320
6 2.985984
7 3.583181
8 4.299817
9 5.159780
dtype: float64
I wonder how I could avoid to use this for loop.
I had a look at
https://fr.wikipedia.org/wiki/Suite_g%C3%A9om%C3%A9trique
and found that u_n can be expressed as: u_n = u_{n_0} * q^{n-n_0}
So I did that
n0 = 0
N = 10
n_array = np.arange(n0, n0 + N, 1)
un0 = 1
q = 1.2
u = pd.Series(map(lambda n: un0 * q ** (n - n0), n_array), index = n_array)
That's ok... but I'm looking for a way to define it in a recurrent way like
u_n0 = 1
u_n = u_{n-1} * 1.2
But I don't see how to do it using Python / Pandas / Numpy... I wonder if it's possible.

Another possibility, that is probably more computationally efficient than using exponentiation:
>>> N, un0, q = 10, 1, 1.2
>>> u = np.empty((N,))
>>> u[0] = un0
>>> u[1:] = q
>>> np.cumprod(u)
array([ 1. , 1.2 , 1.44 , 1.728 , 2.0736 ,
2.48832 , 2.985984 , 3.5831808 , 4.29981696, 5.15978035])

Use numpy.logspace
>>> import numpy
>>> N=10
>>> u=numpy.logspace(0,N,num=N, base=1.2, endpoint=False)
>>> print u
[ 1. 1.2 1.44 1.728 2.0736 2.48832
2.985984 3.5831808 4.29981696 5.15978035]

Here is how it works for me in a Pandas series:
N = 10
n0 = 0
n_array = np.arange(n0, n0 + N, 1)
u = pd.Series(index = n_array)
u[n0] = 1
q = 1.2
# option 1:
u = pd.Series(u[n0]*q**(u.index.values - n0), index = n_array)
# or option 2 with cumprod
u[1:] = q
u = u.cumprod()

Just defining u(u0, q, n) function should work:
def u(u0, q, n):
return u0 if n==0 else q*u(u0, q, n-1)

Itertools helps:
from itertools import accumulate
import operator
import pandas as pd
d, f, n = 1.2, 1, 10 # degree, first element, number
pd.Series([* accumulate([f]+[d] * (n-1), func = operator.mul)])
result is:
0 1.000000
1 1.510000
2 2.280100
3 3.442951
4 5.198856
5 7.850273
6 11.853912
7 17.899406
8 27.028104
9 40.812437
dtype: float64

I think #Ashish's solution with np.cumprod is the simplest but if you are willing to define a generator somewhere then this is probably the most computationally efficient solution:
def geometric_series_generator(x, r, n):
"""Generate a geometric series of length n, starting
at x and increasing by the ratio r.
"""
for i in range(n):
yield x
x = x*r
N = 10
u0 = 1
r = 1.2
gen = geometric_series_generator(u0, r, N)
geom_series = np.fromiter(gen, float, count=N)
print(pd.Series(geom_series, index=np.arange(0, N, 1)))
Output:
0 1.000000
1 1.200000
2 1.440000
3 1.728000
4 2.073600
5 2.488320
6 2.985984
7 3.583181
8 4.299817
9 5.159780
dtype: float64

Related

Derivative On Python

Hi I make some derivative Program on Python, but the result isn't same as what i expected,
This is the result as what i want to be :
f(x) = x^2 - 8x + 25
f'(x) = 2x -8
0 = 2x - 8
8 = 2x
4 = x
x = 4
i want x to be equal to 4
and here's the code :
import sympy as sp
from sympy import *
p = 8
m = 25
f = x**2 - p*x + m
f_prime = f.diff(x)
f = lambdify(x, f)
f_prime = lambdify(x, f_prime)
f_prime(2)
the result is -4
how to solve this problem?
Thankyou
You have to define x as a symbolic variable (otherwise code will not compile), lambdify f_prime and solve the equation f_prime(x) = 0
from sympy import *
p = 8
m = 25
x = symbols('x')
f = x**2 - p*x + m
f_prime = f.diff(x)
print (f_prime)
f_prime = lambdify(x, f_prime)
print(solve(f_prime(x))[0])
2*x - 8
4

How to create a new smaller array from a larger array, taking the average of the old array's elements?

Lets say I have an array of length n:
a = np.arange(1, 20, 0.5).tolist()
I want to create a new array of a smaller size of length m, where m < n, and fill this array with the average values of the older array. For example, array a is 38 in length, but I want to create array b of length 15. The first element of b would be the average value of the first two or three elements in a.
Another example: if I had a vector of length 1491 and wanted to reshape it to an array of length 200, 1491 cannot be perfectly divided by 200 right? The same is true if array a was 4442 but array b was still 200, a different number of values will be needed to get the average values to put into b this time.
Is there a suitable way to do this?
If for example you have array of size 20 a = np.arange(20) then you can reshape it to new 2D shape a = a.reshape(10, 2), second dimension says how many consequent numbers you want to average, in my example you average each 2 consequent numbers, then you compute average reducing 2nd dimension a = a.mean(axis = 1) thus having resulting array of size 10. Full code:
Try it online!
import numpy as np
a = np.arange(20)
print(a)
a = a.reshape(10, 2)
a = a.mean(axis = 1)
print(a)
Output:
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
[ 0.5 2.5 4.5 6.5 8.5 10.5 12.5 14.5 16.5 18.5]
If you for some reason have such b size that a size is not divisible evenly by b size, then you can do next thing - some elements on the left group in blocks of size a_size // b_size + 1 and some on the right in blocks of size a_size // b_size. Next code does this computation (example input a size is 18, desired b size is 5):
Try it online!
import numpy as np
a = np.arange(18)
b_size = 5
print('a =', a)
# bl = a.size // b_size
# l + r = b_size
# l * (bl + 1) + r * bl = a_size
# l = a_size - b_size * bl
bl = a.size // b_size
l = a.size - b_size * bl
r = b_size - l
print('a_size =', a.size, 'b_size =', b_size, 'left =', l,
'right =', r, 'block_left =', bl + 1, 'block_right =', bl)
assert l * (bl + 1) + r * bl == a.size
al, ar = a[:l * (bl + 1)], a[l * (bl + 1):]
al = al.reshape(l, bl + 1)
ar = ar.reshape(r, bl)
b = np.concatenate((al.mean(axis = 1), ar.mean(axis = 1)))
print('b =', b)
Output:
a = [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17]
a_size = 18 b_size = 5 left = 3 right = 2 block_left = 4 block_right = 3
b = [ 1.5 5.5 9.5 13. 16. ]

vectorization of multiple return of a complex function in a dataframe

I am trying to plot various data including complex vectors.Thanks to contributors see answers https://stackoverflow.com/a/64480659/13953414, i managed to generate the dataframes but i get stuck when i want to add some additional calculations. i get an error :
df['T_depth'] = (math.sqrt(D / (4 * (math.pi) * frequency)) / 1e-6),
TypeError: only size-1 arrays can be converted to Python scalars
all calculations starting from T_depth are not executed due to a format issue. the function were vectorized but yet I cannot execute and save the df.
from mpmath import *
import numpy as np
import cmath
import math
import pandas as pd
mp.dps = 15; mp.pretty = True
a = mpf(0.25)
b = mpf(0.25)
z = mpf(0.75)
frequency = np.arange(1, 50, 10) # frequency range
bh = np.arange(10e-6, 35e-6, 10e-6) #10e-6 # width
print(bh)
D = 1e-6 #7.8e-4 # diffusivity
gamma = 0.5772 # Euler constant
# Cartesian product of input variables
idx = pd.MultiIndex.from_product([bh, frequency], names=["bh", "frequency"])
df = pd.DataFrame(index=idx).reset_index()
# Omega is vectorized naturally.
omega = (df["bh"].values**2 * df["frequency"].values) * (2 * math.pi / D)
# vectorize meijerg() only, so other operations won't interrupt with this
def f_u(omega_elem):
# u = (-j/(math.pi * omega_elem)) * meijerg([[1, 3/2], []], [[1, 1], [0.5, 0]], j*omega_elem)
return (-j/(math.pi * omega_elem)) * meijerg([[1, 3/2], []], [[1, 1], [0.5, 0]], j*omega_elem)
f_u_vec = np.vectorize(f_u, otypes=[np.complex128]) # output complex
u = f_u_vec(omega)
def asympt(omega_elem):
return (4/(math.pi))*(-(1/2)*np.log(omega_elem) + 3/2 - gamma - j*((math.pi)/4))
asympt_vec = np.vectorize(asympt, otypes=[np.complex128]) # output complex
v = asympt_vec(omega)
#is it possible to merge these 2 aforementioned operation in one method?
df["Re"] = np.real(u)
df["Im"] = np.imag(u)
df["asympt_R"] = np.real(v)
df["asympt_Im"] = np.imag(v)
# the following operations cannot be executed
df['T_depth'] = (math.sqrt(D / (4 * (math.pi) * frequency)) / 1e-6)
df['amplitude'] = math.sqrt(np.real(u)**2 + np.imag(u)**2)
df['phase'] = math.degrees(math.atan(np.imag(u) / np.real(u)))
df['thermal_freq'] = 2 * (math.pi) * frequency
print(df)
np.vectorize can return multiple "columns" as a tuple of arrays. Here I showcase how to add a "column" to the vectorized function and how to rearrange them.
def f_u(omega_elem):
val1 = (-j / (math.pi * omega_elem)) * meijerg([[1, 3 / 2], []], [[1, 1], [0.5, 0]], j * omega_elem)
asympt = (4 / (math.pi)) * (-(1 / 2) * np.log(omega_elem) + 3 / 2 - gamma - j * ((math.pi) / 4))
return val1, asympt
# return a tuple of array. Remember to assign two otypes.
f_u_vec = np.vectorize(f_u, otypes=[np.complex128, np.complex128])
tup = f_u_vec(omega) # tuple of arrays: (val1, asympt)
df["Re"] = np.real(tup[0]) # val1
df["Im"] = np.imag(tup[0])
df["asympt_R"] = np.real(tup[1]) # asympt
df["asympt_Im"] = np.imag(tup[1])
# result
df
Out[94]:
bh frequency Re Im asympt_R asympt_Im
0 0.00001 1 5.868486 -0.999374 5.868401 -1.0
1 0.00001 11 4.342982 -0.994876 4.341854 -1.0
2 0.00001 21 3.932365 -0.991121 3.930198 -1.0
3 0.00001 31 3.685457 -0.987696 3.682257 -1.0
4 0.00001 41 3.508498 -0.984488 3.504268 -1.0
5 0.00002 1 4.986257 -0.997867 4.985859 -1.0
6 0.00002 11 3.463849 -0.983559 3.459311 -1.0
7 0.00002 21 3.056269 -0.972212 3.047656 -1.0
8 0.00002 31 2.812349 -0.962168 2.799715 -1.0
9 0.00002 41 2.638332 -0.952979 2.621726 -1.0

how do I card shuffle a pandas series quickly

suppose I have the pd.Series
import pandas as pd
import numpy as np
s = pd.Series(np.arange(10), list('abcdefghij'))
I'd like to "shuffle" this series like a deck of cards by interweaving the top half with the bottom half.
I'd expect results like this
a 0
f 5
b 1
g 6
c 2
h 7
d 3
i 8
e 4
j 9
dtype: int32
Conclusions
final function
def perfect_shuffle(s):
n = s.values.shape[0] # get length of s
l = (n + 1) // 2 * 2 # get next even number after n
# use even number to reshape and only use n of them after ravel
a = np.arange(l).reshape(2, -1).T.ravel()[:n]
# construct new series slicing both values and index
return pd.Series(s.values[a], s.index.values[a])
demonstration
s = pd.Series(np.arange(11), list('abcdefghijk'))
print(perfect_shuffle(s))
a 0
g 6
b 1
h 7
c 2
i 8
d 3
j 9
e 4
k 10
f 5
dtype: int64
order='F' vs T
I had suggested using T.ravel() as opposed to ravel(order='F')
After investigation, it hardly matters but ravel(order='F') is better for larger arrays.
d = pd.DataFrame(dict(T=[], R=[]))
for n in np.power(10, np.arange(1, 8)):
a = np.arange(n).reshape(2, -1)
stamp = pd.datetime.now()
for _ in range(100):
a.ravel(order='F')
d.loc[n, 'R'] = (pd.datetime.now() - stamp).total_seconds()
stamp = pd.datetime.now()
for _ in range(100):
a.T.ravel()
d.loc[n, 'T'] = (pd.datetime.now() - stamp).total_seconds()
d
d.plot()
Thanks unutbu and Warren Weckesser
In then special case where the length of the Series is even, you can to do a perfectly shuffle by reshaping its values into two rows and then using ravel(order='F') to read the items off in Fortran order:
In [12]: pd.Series(s.values.reshape(2,-1).ravel(order='F'), s.index)
Out[12]:
a 0
b 5
c 1
d 6
e 2
f 7
g 3
h 8
i 4
j 9
dtype: int64
Fortran order makes the left-most axis increment fastest. So in a 2D array the
values are read off by going down the rows of one column before progressing to
the next column. This has the effect of interleaving the values, compared to the
usual C-order.
In the general case where the length of the Series could be odd,
perhaps the fastest way is to reassign the values using shifted slices:
import numpy as np
import pandas as pd
def perfect_shuffle(ser):
arr = ser.values
result = np.empty_like(arr)
N = (len(arr)+1)//2
result[::2] = arr[:N]
result[1::2] = arr[N:]
result = pd.Series(result, index=ser.index)
return result
s = pd.Series(np.arange(11), list('abcdefghijk'))
print(perfect_shuffle(s))
yields
a 0
b 6
c 1
d 7
e 2
f 8
g 3
h 9
i 4
j 10
k 5
dtype: int64
To add to #unutbu's answer, some benchmarks:
>>> import timeit
>>> import numpy as np
>>>
>>> setup = '''
... import pandas as pd
... import numpy as np
... s = pd.Series(list('abcdefghij'), np.arange(10))
... '''
>>>
>>> funcs = ['s[np.random.permutation(s.index)]', "pd.Series(s.values.reshape(2,-1).ravel(order='F'), s.index)",
... 's.iloc[np.random.permutation(s.index)]', "s.values.reshape(-1, 2, order='F').ravel()"]
>>>
>>> for f in funcs:
... print(f)
... print(min(timeit.Timer(f, setup).repeat(3, 50)))
...
s[np.random.permutation(s.index)]
0.029795593000017107
pd.Series(s.values.reshape(2,-1).ravel(order='F'), s.index)
0.0035402200010139495
s.iloc[np.random.permutation(s.index)]
0.010904800990829244
s.values.reshape(-1, 2, order='F').ravel()
0.00019640100072138011
The final f in funcs is > 99% faster than the first np.random.permutation approach, so that's probably your best bet.

How to handle a variable number of nested for loops?

I have a dict for which I would like to find all combinations of sums of values, multiplied by an increasing factor. A possible code for the case where the size of the dict is 2:
# data and n come from elsewhere
data = {'a': 1, 'b': 2}
n = 3
for x in xrange(0, n):
for y in xrange(0, n):
print("{0} * {1} + {2} * {3} = {4}".format(x, data['a'], y, data['b'], x * data['a'] + y * data['b']))
which gives
0 * 1 + 0 * 2 = 0
0 * 1 + 1 * 2 = 2
0 * 1 + 2 * 2 = 4
1 * 1 + 0 * 2 = 1
(...)
2 * 1 + 2 * 2 = 6
The problem I have is that the number of elements in the dict will vary, so the number of nested for should be changing as well. Is there a better way to code such a problem to accommodate such a variable dict?
You can replace your nested loop with a single loop over the cartesian product
from itertools import product
for x, y in product(range(n), repeat=2):
...
This isn't too useful in itself as you still hardcode 2 variables in there. But it leads us on the the next point - itertools.product yields tuples as you iterate over it
from itertools import product
num_loops = 5 # len(data) in your case
for item in product(range(n), repeat=num_loops):
... # item is a 5-tuple

Categories