I'm written this long winded answer because I suspect you don't quite understand the value of "no loop" when working with numpy.
So you have 2 arrays of the same length:
In [256]: a = np.arange(0,15,1)
...: b = np.arange(0,150,10)
In [257]: a
Out[257]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
In [258]: b
Out[258]:
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120,
130, 140])
In [259]: a.shape
Out[259]: (15,)
In [260]: b.shape
Out[260]: (15,)
A straight forward print of each pair - this would even be faster if a and b were lists instead of arrays, but the real time limit is the print, not the iteration. (I'm using the newish f formating for convenience.)
In [261]: for i,j in zip(a,b):print(f'when a is {i}, b is {j}')
when a is 0, b is 0
when a is 1, b is 10
when a is 2, b is 20
....
when a is 13, b is 130
when a is 14, b is 140
We can make a 2d array from the two arrays:
In [262]: np.array((a,b))
Out[262]:
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14],
[ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120,
130, 140]])
And if you prefer a column display:
In [263]: np.array((a,b)).T
Out[263]:
array([[ 0, 0],
[ 1, 10],
[ 2, 20],
[ 3, 30],
...
[ 13, 130],
[ 14, 140]])
That makes a (15,2) array. There are several other ways of constructing such an array.
While the general layout matches your print, including all the extra text will take more work. numpy isn't optimized for string work; it's a numeric tool. If you want the fancy formatting stick with the python loop.
np.savetxt writes a csv file. It iterates on the rows, and for each does a formatted write like:
In [268]: for row in Out[263]: print('When a is %d, b is %d'%tuple(row))
When a is 0, b is 0
When a is 1, b is 10
When a is 2, b is 20
When a is 3, b is 30
In [270]: np.savetxt('test.txt', Out[263], fmt='when a is %d, b is %d')
In [271]: cat test.txt
when a is 0, b is 0
when a is 1, b is 10
when a is 2, b is 20
when a is 3, b is 30