Skip to main content

💟 Lovely NumPy

Read full docs

More lovely stuff

Working with numbers
Community

Install

pip install lovely-numpy

or

conda install -c conda-forge lovely-numpy

How to use

How often do you find yourself debugging NumPy code? You dump your variable to the cell output, and see this:

numbers
array([[[-0.3541, -0.1975, -0.6715],
        [-0.3369, -0.1975, -0.9853],
        ...,
        [-0.4739, -0.3725, -0.689 ],
        [ 2.2489,  2.4111,  2.396 ]],

       [[-0.4054, -0.25  , -0.7238],
        [-0.4226, -0.2325, -1.0724],
        ...,
        [-0.8507, -0.6702, -1.0201],
        [ 2.1633,  2.3585,  2.3263]],

       ...,

       [[-0.8507, -0.3901, -1.1944],
        [-0.7822, -0.2325, -1.4559],
        ...,
        [-1.5014, -1.2304, -1.4733],
        [ 2.1804,  2.4111,  2.4308]],

       [[-0.8335, -0.4076, -1.2293],
        [-0.8164, -0.285 , -1.5256],
        ...,
        [-1.5528, -1.2829, -1.5256],
        [ 2.1119,  2.341 ,  2.3611]]], shape=(196, 196, 3), dtype=float32)

Was it really useful for you, as a human, to see all these numbers?

What is the shape? The size?
What are the statistics?
Are any of the values nan or inf?
Is it an image of a man holding a tench?

from lovely_numpy import lo

Lo and behold!

lo(numbers)
array[196, 196, 3] f32 n=115248 (450 KiB) x∈[-2.118 |▁▅█▇▂▁▁▂▂▁| 2.640] μ=-0.388 σ=1.073

Better, eh?

lo(numbers[1,:6,1]) # Still shows values if there are not too many.
array[6] f32 x∈[-0.408, -0.232] μ=-0.340 σ=0.075 [-0.250, -0.232, -0.338, -0.408, -0.408, -0.408]
spicy = numbers[0,:12,0].copy()

spicy[0] *= 10000
spicy[1] /= 10000
spicy[2] = float('inf')
spicy[3] = float('-inf')
spicy[4] = float('nan')

spicy = spicy.reshape((2,6))
lo(spicy) # Spicy stuff
array[2, 6] f32 n=12 x∈[-3.541e+03, -3.369e-05] μ=-393.776 σ=1.113e+03 +Inf! -Inf! NaN!
lo(np.zeros((10, 10))) # A zero array - make it obvious
array[10, 10] n=100 all_zeros
lo(spicy, verbose=True)
array[2, 6] f32 n=12 x∈[-3.541e+03, -3.369e-05] μ=-393.776 σ=1.113e+03 +Inf! -Inf! NaN!
array([[-3540.5432,    -0.    , ...,        nan,    -0.4054],
       [   -0.4226,    -0.4911, ...,    -0.5424,    -0.5082]],
      shape=(2, 6), dtype=float32)

Going .deeper

lo(numbers.transpose(2,1,0)).deeper
array[3, 196, 196] f32 n=115248 (450 KiB) x∈[-2.118 |▂▅█▇▂▁▁▂▂▁| 2.640] μ=-0.388 σ=1.073
  array[196, 196] f32 n=38416 x∈[-2.118 |▂▃▅█▂▁▁▂▃▁| 2.249] μ=-0.324 σ=1.036
  array[196, 196] f32 n=38416 x∈[-1.966 |▂▂▅█▂▁▁▁▂▁| 2.429] μ=-0.274 σ=0.973
  array[196, 196] f32 n=38416 x∈[-1.804 |▃█▃▁▁▁▁▁▂▁| 2.640] μ=-0.567 σ=1.178
# You can go deeper if you need to
lo(numbers[:3,:4]).deeper(2)
array[3, 4, 3] f32 n=36 x∈[-1.125, -0.197] μ=-0.563 σ=0.280
  array[4, 3] f32 n=12 x∈[-0.985, -0.197] μ=-0.487 σ=0.259
    array[3] f32 x∈[-0.672, -0.197] μ=-0.408 σ=0.197 [-0.354, -0.197, -0.672]
    array[3] f32 x∈[-0.985, -0.197] μ=-0.507 σ=0.343 [-0.337, -0.197, -0.985]
    array[3] f32 x∈[-0.881, -0.303] μ=-0.530 σ=0.252 [-0.405, -0.303, -0.881]
    array[3] f32 x∈[-0.776, -0.303] μ=-0.506 σ=0.199 [-0.440, -0.303, -0.776]
  array[4, 3] f32 n=12 x∈[-1.072, -0.232] μ=-0.571 σ=0.281
    array[3] f32 x∈[-0.724, -0.250] μ=-0.460 σ=0.197 [-0.405, -0.250, -0.724]
    array[3] f32 x∈[-1.072, -0.232] μ=-0.576 σ=0.360 [-0.423, -0.232, -1.072]
    array[3] f32 x∈[-0.968, -0.338] μ=-0.599 σ=0.268 [-0.491, -0.338, -0.968]
    array[3] f32 x∈[-0.968, -0.408] μ=-0.651 σ=0.235 [-0.577, -0.408, -0.968]
  array[4, 3] f32 n=12 x∈[-1.125, -0.285] μ=-0.631 σ=0.280
    array[3] f32 x∈[-0.828, -0.303] μ=-0.535 σ=0.219 [-0.474, -0.303, -0.828]
    array[3] f32 x∈[-1.125, -0.285] μ=-0.628 σ=0.360 [-0.474, -0.285, -1.125]
    array[3] f32 x∈[-1.020, -0.390] μ=-0.651 σ=0.268 [-0.542, -0.390, -1.020]
    array[3] f32 x∈[-1.003, -0.478] μ=-0.708 σ=0.219 [-0.645, -0.478, -1.003]

Now in .rgb color

The important queston - is it our man?

lo(numbers).rgb

Maaaaybe? Looks like someone normalized him.

in_stats = ( (0.485, 0.456, 0.406),     # mean
             (0.229, 0.224, 0.225) )    # std

# numbers.rgb(in_stats, cl=True) # For channel-last input format
lo(numbers).rgb(denorm=in_stats)

# Denorm is used to convert the input to [0..1], which is then mapped to RGB.

# lo(numbers).rgb(denorm="imagenet") # same as above
# lo(numbers).rgb(denorm="symmetric") # [-1 .. 1] input
# lo(numbers).rgb(denorm="minmax") # Use the min/max elements in each channel to scale the input to [0..1]

It’s indeed our hero, the Tenchman!

See the .chans

# .chans will map values betwen [-1,1] to colors.
# Make our values fit into that range to avoid clipping.
mean = np.array(in_stats[0])
std = np.array(in_stats[1])
numbers_01 = (numbers*std + mean).clip(0,1)
lo(numbers_01)
array[196, 196, 3] n=115248 (900 KiB) x∈[0. |▃▆▆█▂▁▁▁▃▂| 1.000] μ=0.361 σ=0.248
lo(numbers_01).chans

Grouping

# Make 8 images with progressively higher brightness and stack them 2x2x2.
eight_images = (np.stack([numbers]*8) + np.linspace(-2, 2, 8)[:,None,None,None])
eight_images = (eight_images
                     *np.array(in_stats[1])
                     +np.array(in_stats[0])
                ).clip(0,1).reshape(2,2,2,196,196,3)

lo(eight_images)
array[2, 2, 2, 196, 196, 3] n=921984 (7.03 MiB) x∈[0. |█▃▃▃▃▃▃▂▂▃| 1.000] μ=0.382 σ=0.319
lo(eight_images).rgb

Histogram

lo(numbers+3).plt

lo(numbers+3).plt(center="mean", max_s=1000)

lo(numbers+3).plt(center="range")

Options | Docs

from lovely_numpy import set_config, config, lovely
set_config(precision=5, sci_mode=True, color=False)
lo(np.array([1.,2,np.nan]))
array[3] μ=1.50000e+00 σ=5.00000e-01 NaN! [1.00000e+00, 2.00000e+00, nan]
set_config(precision=None, sci_mode=None, color=None) # None -> Reset to defaults
lo(np.array([1.,2,np.nan]))
array[3] μ=1.500 σ=0.500 NaN! [1.000, 2.000, nan]
# Or with config context manager.
with config(sci_mode=True):
    print(lo(np.array([1,2,3])))

print(lo(np.array([1,2,3])))
array[3] i64 x∈[1, 3] μ=2.000e+00 σ=8.165e-01 [1, 2, 3]
array[3] i64 x∈[1, 3] μ=2.000 σ=0.816 [1, 2, 3]

Without Lo

from lovely_numpy import rgb, chans, plot
lovely(numbers) # Returns `str`, that's why you see ''.
# Note:  lo(x) returns a wrapper object with a `__repr__` and other methods.
'array[196, 196, 3] f32 n=115248 (450 KiB) x∈[-2.118 |▁▅█▇▂▁▁▂▂▁| 2.640] μ=-0.388 σ=1.073'
rgb(numbers, denorm=in_stats) # OR: denorm='imagenet'

chans(numbers*0.3+0.5)

plot(numbers)

Matplotlib integration | Docs

lo(numbers).rgb(in_stats).fig # matplotlib figure

lo(numbers).plt.fig.savefig('pretty.svg') # Save it
!file pretty.svg; rm pretty.svg
pretty.svg: SVG Scalable Vector Graphics image
fig = plt.figure(figsize=(8,3))
fig.set_constrained_layout(True) # type: ignore
gs = fig.add_gridspec(2,2)
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[1,1:])

ax2.set_axis_off()
ax3.set_axis_off()

lo(numbers_01).plt(ax=ax1)
lo(numbers_01).rgb(ax=ax2)
lo(numbers_01).chans(ax=ax3);

Metadata

Release files for lovely-numpy 0.2.24

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lovely-numpy 0.2.24
File Size Uploaded
lovely_numpy-0.2.24.tar.gz 27.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lovely-numpy 0.2.24
File Interpreter ABI Platform
lovely_numpy-0.2.24-py3-none-any.whl Python 3 none any Details

Total release size: 55.5 kB

Release files / lovely_numpy-0.2.24.tar.gz

Download URL lovely_numpy-0.2.24.tar.gz
Size 27.3 kB
Tags Source
SHA-256 checksum
How to use checksums
683ae278cb880d33e33757383675f073ef0b3897e49d083431e527cd0c3fd531
BLAKE2b-256 checksum
How to use checksums
db9d04674d9d7270616a17d71ca689d453654e4296df2d404acb700d2464f9a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.11

Release files / lovely_numpy-0.2.24-py3-none-any.whl

Download URL lovely_numpy-0.2.24-py3-none-any.whl
Size 28.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
10042ee8eac109c6e1772aa9a72188b12088b7b5c918f7aba241b53bc97ab1c3
BLAKE2b-256 checksum
How to use checksums
70bdcb98519aa658b488710aef0d5bac1435e7b95756b7e28c8c97ca394f5260
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.11
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page