Another option is to make a structured array, with a mix of integer and string fields.
In [252]: import numpy.lib.recfunctions as rf
In [258]: X = [[3, 'aa', 10],
...: [1, 'bb', 22],
...: [2, 'cc', 28],
...: [5, 'bb', 32],
...: [4, 'cc', 32]]
In [259]: dt = np.dtype('i,U10,i')
In [260]: dt
Out[260]: dtype([('f0', '<i4'), ('f1', '<U10'), ('f2', '<i4')])
Recent (1.16) numpy has a function that converts unstructured arrays (e.g. the string dtype) to structured:
In [261]: Y = rf.unstructured_to_structured(np.array(X), dt)
In [262]: Y
Out[262]:
array([(3, 'aa', 10), (1, 'bb', 22), (2, 'cc', 28), (5, 'bb', 32),
(4, 'cc', 32)],
dtype=[('f0', '<i4'), ('f1', '<U10'), ('f2', '<i4')])
Fields are accessed by name:
In [264]: Y['f0']
Out[264]: array([3, 1, 2, 5, 4], dtype=int32)
In [265]: Y['f1']
Out[265]: array(['aa', 'bb', 'cc', 'bb', 'cc'], dtype='<U10')
Converting X to a list of tuples will work just as well
In [266]: np.array([tuple(row) for row in X], dtype=dt)
Out[266]:
array([(3, 'aa', 10), (1, 'bb', 22), (2, 'cc', 28), (5, 'bb', 32),
(4, 'cc', 32)],
dtype=[('f0', '<i4'), ('f1', '<U10'), ('f2', '<i4')])
The object array and structured array each have their advantages and disadvantages. So which is better will depend on what you intend to do with array. For that matter, the original list may, for many purposes, be just as good. None has the same processing speed (for math operations) as a 2d numeric array.