As stated by @couka, a numpy array can only have one type of data in it. Though, you can cheat a little, for numpy allows the object dtype. For instance:
>>> import numpy as np
>>> test = ["a", 1, 2, 3]
>>> test_array = np.array(test)
>>> test_array
array(['a', '1', '2', '3'], dtype='<U1')
>>> test_array * 2
numpy.core._exceptions.UFuncTypeError: ufunc 'multiply' did not contain a loop with signature matching types (dtype('<U3'), dtype('<U3')) -> dtype('<U3')
>>> test_array_object = np.array(test, dtype="O")
>>> test_array_object
array(['a', 1, 2, 3], dtype=object)
>>> test_array_object * 2
array(['aa', 2, 4, 6], dtype=object)
When the dtype of the array is object and an operation is performed, numpy will simply apply the corrersponding operation on its elements. For instance, when multiplying an array with an integer, numpy will call the __mul__ method of each object in the array.
This method has several drawbacks though. If you only want to apply the operation on the integers, you have to do some indexing:
>>> test_array_object[1:] *= 2
>>> test_array_object
array(['a', 2, 4, 6], dtype=object)
More importantly, numpy can't use every optimization it usually uses for integer arrays, so you would have a cost overhead in speed execution by doing so.
I think the simplest method is to simply use a dataclass to hold your data and define the operations you want to apply on it, like this:
from dataclasses impoort dataclass
import numpy as np
@dataclass
class NamedArray:
name: str
array: np.ndarray
which you would then use like this:
>>> test = ["a", 1., 2., 3.]
>>> named_test = NamedArray(test[0], np.array(test[1:]))
>>> named_test
NamedArray(name='a', array=array([1, 2, 3]))
>>> named_test.array
array([1., 2., 3.])
>>> named_test.array.dtype
dtype('float64')
You are then to free to work with named_test.array to do your computations on an array, while keeping numpy's optimizations on the operations you perform and without having to care about the str held in named_test.name.