86

I want to combine 2 parts of the same array to make a complex array:

Data[:,:,:,0] , Data[:,:,:,1]

These don't work:

x = np.complex(Data[:,:,:,0], Data[:,:,:,1])
x = complex(Data[:,:,:,0], Data[:,:,:,1])

Am I missing something? Does numpy not like performing array functions on complex numbers? Here's the error:

TypeError: only length-1 arrays can be converted to Python scalars
J Agustin Barrachina
  • 3,501
  • 1
  • 32
  • 52
Duncan Tait
  • 1,997
  • 4
  • 20
  • 24

9 Answers9

102

This seems to do what you want:

numpy.apply_along_axis(lambda args: [complex(*args)], 3, Data)

Here is another solution:

# The ellipsis is equivalent here to ":,:,:"...
numpy.vectorize(complex)(Data[...,0], Data[...,1])

And yet another simpler solution:

Data[...,0] + 1j * Data[...,1]

PS: If you want to save memory (no intermediate array):

result = 1j*Data[...,1]; result += Data[...,0]

devS' solution below is also fast.

Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
  • Same error I'm afraid: TypeError: only length-1 arrays can be converted to Python Scalars – Duncan Tait Apr 08 '10 at 09:42
  • @Duncan: I updated the original answer after performing the test. It seems to be working, now. – Eric O. Lebigot Apr 08 '10 at 09:43
  • 1
    thanks alot that does work. It's VERY slow though (as you might expect - as it's not really a numpy function), it takes 5 seconds per loop now instead of 0.1 – Duncan Tait Apr 08 '10 at 09:45
  • @Duncan: I added two other solutions: it may be worth to time them too. If this works for you, please thumb up the answer! – Eric O. Lebigot Apr 08 '10 at 09:52
  • Excellent they're both much faster :) – Duncan Tait Apr 08 '10 at 10:01
  • -1, the apply_along_axis and vectorize solution is unidiomatic. Using native numpy ufuncs is idiomatic and much faster. – Pavel Bazant Jan 28 '14 at 15:13
  • I agree that the first two solutions I proposed are not as nice as the last one, but what better solution with ufuncs do you have in mind? Please share (possibly in an answer)! – Eric O. Lebigot Jan 28 '14 at 20:29
  • The last solution is fine and idiomatic, but devS's solution in the answer below is the most efficient one, as it avoids unnecessary intermediates. – Pavel Bazant Jan 29 '14 at 16:38
  • devS's idea is indeed interesting, as it avoids one array creation. (I still don't understand the relationship with ufuncs, though.) – Eric O. Lebigot Jan 29 '14 at 22:34
  • I was not specific enough, sorry. I wanted to say that it is efficient to use the built-in ufuncs (in our case + and *), but it is not very efficient to use vectorize or apply_along_axis, as the looping then in general happens in Python. – Pavel Bazant Feb 05 '14 at 21:55
  • I see, thanks. Indeed, the `vectorize()` approach is slow compared to the solutions that follow. – Eric O. Lebigot Feb 07 '14 at 13:21
61

There's of course the rather obvious:

Data[...,0] + 1j * Data[...,1]
Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
Pierre GM
  • 19,809
  • 3
  • 56
  • 67
31

If your real and imaginary parts are the slices along the last dimension and your array is contiguous along the last dimension, you can just do

A.view(dtype=np.complex128)

If you are using single precision floats, this would be

A.view(dtype=np.complex64)

Here is a fuller example

import numpy as np
from numpy.random import rand
# Randomly choose real and imaginary parts.
# Treat last axis as the real and imaginary parts.
A = rand(100, 2)
# Cast the array as a complex array
# Note that this will now be a 100x1 array
A_comp = A.view(dtype=np.complex128)
# To get the original array A back from the complex version
A = A.view(dtype=np.float64)

If you want to get rid of the extra dimension that stays around from the casting, you could do something like

A_comp = A.view(dtype=np.complex128)[...,0]

This works because, in memory, a complex number is really just two floating point numbers. The first represents the real part, and the second represents the imaginary part. The view method of the array changes the dtype of the array to reflect that you want to treat two adjacent floating point values as a single complex number and updates the dimension accordingly.

This method does not copy any values in the array or perform any new computations, all it does is create a new array object that views the same block of memory differently. That makes it so that this operation can be performed much faster than anything that involves copying values. It also means that any changes made in the complex-valued array will be reflected in the array with the real and imaginary parts.

It may also be a little trickier to recover the original array if you remove the extra axis that is there immediately after the type cast. Things like A_comp[...,np.newaxis].view(np.float64) do not currently work because, as of this writing, NumPy doesn't detect that the array is still C-contiguous when the new axis is added. See this issue. A_comp.view(np.float64).reshape(A.shape) seems to work in most cases though.

IanH
  • 10,250
  • 1
  • 28
  • 32
  • 1
    +1: Very lucid explanation of the limitations of the method. You might want to add explicitly another limitation (shared memory between `A_comp` and `A`), and also an advantage of this method (speed). – Eric O. Lebigot Apr 13 '14 at 04:14
20

This is what your are looking for:

from numpy import array

a=array([1,2,3])
b=array([4,5,6])

a + 1j*b

->array([ 1.+4.j,  2.+5.j,  3.+6.j])
nadapez
  • 2,603
  • 2
  • 20
  • 26
  • This is only a partial duplicate of earlier answers like Pierre GM's or mine: I think that its only effect is to take people's time for almost no added value (beyond the example), so I would suggest that you delete it. – Eric O. Lebigot Feb 14 '17 at 11:55
  • 6
    14 people disagree! While the depth is weak and it doesn't deserve a checkmark, this example got me fastest to what I needed. – supergra Sep 07 '19 at 23:20
16

I am python novice so this may not be the most efficient method but, if I understand the intent of the question correctly, steps listed below worked for me.

>>> import numpy as np
>>> Data = np.random.random((100, 100, 1000, 2))
>>> result = np.empty(Data.shape[:-1], dtype=complex)
>>> result.real = Data[...,0]; result.imag = Data[...,1]
>>> print Data[0,0,0,0], Data[0,0,0,1], result[0,0,0]
0.0782889873474 0.156087854837 (0.0782889873474+0.156087854837j)
Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
devS
  • 161
  • 1
  • 2
  • Interesting idea. However, the question is about combining `Data[:,:,:,0]` and `Data[:,:,:,1]` (more complicated than your `a`). Also, instead of using `zeros()`, you should use the faster and more appropriate `empty()`. – Eric O. Lebigot Jan 29 '14 at 22:35
  • 2
    I compared it with the Data[…,0] + 1j * Data[…,1] solution. With Data = random.rand(100,100,1000,2),c=zeros(a.shape[:-1],dtype=complex);c.real = Data[...,0]; c.imag = Data[...,1]; is 2x faster than the straightforward Data[…,0] + 1j * Data[…,1]. Surprisingly, the effect of using empty instead of zeros was negligible. – Pavel Bazant Feb 06 '14 at 08:51
  • 2
    +1. Note: I get the same speed with a variation of my last answer: `result = 1j*Data[...,1]; result += Data[...,0]`. This answer is more natural, though, if a single formula is not used. – Eric O. Lebigot Feb 07 '14 at 13:19
  • I think this is the best answer because the intent is obvious when reading the code; Eric's answers, while functionally correct, are less clear when reading back over the code. – Biggsy Mar 12 '19 at 09:58
  • Well in the underlying framework, there will be a C call to malloc or calloc likely in either case. Now calloc would do a memset to 0 but the time of this operation is extremely fast given it can be done with a single x86 assembly instruction e.g. REP SETZ, mostly just the memory latency might make it take some time, but likely not significant. However numpy may zero for empty anyway making it an alias in effect. – Gregory Morse Nov 04 '22 at 08:05
6
import numpy as np

n = 51 #number of data points
# Suppose the real and imaginary parts are created independently
real_part = np.random.normal(size=n)
imag_part = np.random.normal(size=n)

# Create a complex array - the imaginary part will be equal to zero
z = np.array(real_part, dtype=complex)
# Now define the imaginary part:
z.imag = imag_part
print(z)
Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
1

I use the following method:

import numpy as np

real = np.ones((2, 3))
imag = 2*np.ones((2, 3))

complex = np.vectorize(complex)(real, imag)
# OR
complex = real + 1j*imag
learner
  • 3,168
  • 3
  • 18
  • 35
0

If you really want to eke out performance (with big arrays), numexpr can be used, which takes advantage of multiple cores.

Setup:

>>> import numpy as np
>>> Data = np.random.randn(64, 64, 64, 2)
>>> x, y = Data[...,0], Data[...,1]

With numexpr:

>>> import numexpr as ne
>>> %timeit result = ne.evaluate("complex(x, y)")
573 µs ± 21.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Compared to fast numpy method:

>>> %timeit result = np.empty(x.shape, dtype=complex); result.real = x; result.imag = y
1.39 ms ± 5.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
jawknee
  • 168
  • 1
  • 9
-1

That worked for me:

input:

[complex(a,b) for a,b in zip([1,2,3],[1,2,3])]

output:

[(1+4j), (2+5j), (3+6j)]
Nikolay Frick
  • 2,145
  • 22
  • 17