Python for Business Analytics

Chapter 7
Numerical Analytics with NumPy

Bachelors Program in Business Analytics
Python for Business Analytics   •   Bachelor

Numerical Analytics with NumPy

Introduction

Business analytics frequently involves transforming large collections of numerical observations: sales by product and period, customer-level transaction values, inventory records, portfolio returns, or operational performance measures. Python’s NumPy library provides the numerical foundation for this work. Its central object, the ndarray, stores homogeneous numerical data and supports compact, efficient operations on complete arrays.

NumPy is important not merely because it is faster than repeatedly using Python loops. It also expresses analytical intent clearly. Instead of describing how to visit each observation, an analyst can describe the mathematical operation to be applied to a whole vector or matrix. This style—vectorization—reduces implementation errors and provides a foundation for pandas, SciPy, scikit-learn, and many machine-learning systems.

Learning Objectives

By the end of this chapter, you should be able to:

  • construct and inspect one- and multi-dimensional NumPy arrays;
  • distinguish shape, dimension, size, and data type;
  • use indexing, slicing, masking, and vectorized arithmetic;
  • explain and apply broadcasting rules;
  • aggregate data correctly across rows and columns;
  • perform basic matrix and linear-algebra operations;
  • use random sampling reproducibly for simulation and analysis;
  • identify numerical, interpretive, and governance risks in array-based analysis.

1. The Array as a Numerical Data Structure

A NumPy array is an ordered, typically homogeneous collection of values. Its principal properties are:

PropertyMeaningExample
ndimNumber of axes or dimensions2 for a table
shapeLength along each axis(3, 4)
sizeTotal number of elements12
dtypeStored numerical typefloat64

A one-dimensional array may represent monthly revenue, while a two-dimensional array may represent products by months. Higher-dimensional arrays can represent scenarios, time periods, products, and regions simultaneously.

import numpy as np

sales = np.array([120, 150, 135, 180])
print(sales.shape)   # (4,)
print(sales.ndim)    # 1
print(sales.dtype)

The elements in an array generally share one data type. This makes storage and computation efficient, but it differs from a Python list, which can contain unrelated object types. Explicit construction is often useful:

prices = np.array([19.99, 24.50, 15.75], dtype=np.float64)
units = np.array([10, 8, 12], dtype=np.int64)

Common constructors include np.zeros, np.ones, np.full, np.arange, and np.linspace. arange(start, stop, step) uses a half-open interval and is convenient for integer sequences. linspace(start, stop, num) creates a specified number of evenly spaced values and includes the endpoint by default.

months = np.arange(1, 13)
weights = np.linspace(0.10, 0.30, 5)

For numerical analysis, arrays should normally contain compatible quantities. Mixing revenue measured in dollars with quantities measured in units creates a technically valid array but an analytically meaningless calculation.

2. Indexing, Slicing, and Selection

NumPy uses zero-based indexing. For a two-dimensional array, the first index identifies the row and the second identifies the column.

revenue = np.array([[100, 120, 110],
                    [80,  95,  105]])

revenue[0, 1]      # 120
revenue[:, 0]      # first column
revenue[1, :]      # second row
revenue[:, 1:]     # columns 1 and 2

A slice usually returns a view into the original array rather than an independent copy. Consequently, changing a slice can change the original data:

first_product = revenue[0, :]
first_product[0] = 999       # also changes revenue[0, 0]

Use .copy() when independent data is required. This distinction matters in production analysis because accidental modification of source values can invalidate later results.

Boolean masks select observations satisfying a condition:

sales = np.array([120, 75, 210, 95, 180])
high_sales = sales[sales >= 100]

A compound condition requires elementwise operators: & for “and,” | for “or,” and ~ for “not.” Each condition should be enclosed in parentheses.

mask = (sales >= 100) & (sales < 200)
selected = sales[mask]

The Python keywords and and or do not perform elementwise array logic. Confusing them is a common source of errors.

3. Vectorization and Elementwise Operations

Vectorization applies an operation to all array elements without an explicit Python-level loop. If revenue and cost are arrays of equal shape, profit is calculated as:

πᵢ = Rᵢ − Cᵢ

where πᵢ is profit for observation i, Rᵢ is revenue, and Cᵢ is cost, all measured in the same currency.

revenue = np.array([1200, 1500, 900])
cost = np.array([800, 1000, 700])
profit = revenue - cost
margin = profit / revenue

The resulting profit is [400, 500, 200], and the margins are approximately [0.3333, 0.3333, 0.2222]. A margin is dimensionless because currency units cancel. Division by zero must nevertheless be handled explicitly when revenue can be zero.

Arithmetic operators are elementwise: +, -, , /, //, and . Thus a b is not matrix multiplication. NumPy also supplies universal functions (ufuncs), such as np.sqrt, np.log, np.exp, np.maximum, and np.where.

cost = np.array([40, 55, 80])
price = np.where(cost > 60, cost * 1.25, cost * 1.40)

Here, the pricing rule applies a 25% markup to costs above 60 and a 40% markup otherwise. The analyst should confirm whether “markup” is intended as a proportion of cost. A margin based on selling price is a different measure:

margin = (price − cost) ÷ price

4. Broadcasting

Broadcasting allows NumPy to operate on arrays with compatible but unequal shapes. Conceptually, NumPy aligns shapes from the rightmost dimension. Two dimensions are compatible when they are equal or when one of them is 1; missing leading dimensions are treated as 1.

Suppose rows represent products and columns represent months. A monthly adjustment can be applied to every product:

sales = np.array([[100, 120, 110],
                  [80,  95,  105]])
adjustment = np.array([1.00, 1.05, 0.95])
adjusted = sales * adjustment

The (2, 3) sales array and (3,) adjustment array are compatible. The adjustment is conceptually repeated across the two rows. Broadcasting does not necessarily create a physical copy, which can improve memory efficiency.

A column vector has shape (2, 1) and can be combined with the same sales matrix:

regional_factor = np.array([[1.10], [0.90]])
regional_sales = sales * regional_factor

The factors apply by row. Analysts should inspect shapes before calculation. A successful broadcast can still be conceptually wrong if rows and columns have been misinterpreted.

5. Aggregation and Descriptive Analytics

Aggregation reduces many values to summaries such as totals, means, minima, maxima, and standard deviations. For values x₁, x₂, …, xₙ, the arithmetic mean is:

x̄ = (1 ÷ n) ∑ᵢ₌₁ⁿ xᵢ

where n is the number of observations. The population standard deviation is:

σ = √[(1 ÷ n) ∑ᵢ₌₁ⁿ (xᵢ − μ)²]

where μ is the population mean. When estimating population variability from a sample, NumPy’s np.std uses ddof=0 by default; ddof=1 produces the usual sample standard deviation with denominator n − 1. The choice must match the analytical purpose.

sales = np.array([[100, 120, 110],
                  [80,  95,  105]])

total = sales.sum()
product_totals = sales.sum(axis=1)
monthly_totals = sales.sum(axis=0)
monthly_mean = sales.mean(axis=0)
monthly_sd = sales.std(axis=0, ddof=1)

For a two-dimensional array, axis=0 collapses rows and returns one value per column; axis=1 collapses columns and returns one value per row. This convention should be tested with a small example rather than memorized mechanically.

Other useful functions include np.median, np.percentile, np.min, np.max, np.argmin, np.argmax, and np.cumsum. Missing values represented by np.nan can contaminate ordinary aggregates. Use np.nanmean, np.nansum, and related functions only after deciding whether missingness is ignorable and documenting that decision.

Weighted averages are appropriate when observations have unequal importance. Given values xᵢ and nonnegative weights wᵢ with a positive total weight, the weighted mean is:

x̄w = (∑ᵢ wᵢxᵢ) ÷ (∑ᵢ wᵢ)
prices = np.array([10, 12, 15])
quantities = np.array([100, 50, 20])
weighted_price = np.average(prices, weights=quantities)

This calculates average price per unit across transactions, not the simple average of listed prices.

6. Linear Algebra for Business Models

Matrices represent systems involving multiple variables. If a business has product quantities in vector q and unit prices in vector p, total revenue can be expressed as:

R = pᵀq = ∑ᵢ pᵢqᵢ

where pᵀ is the transpose of the price vector. In NumPy, np.dot(p, q) or p @ q computes this inner product when the shapes are compatible.

prices = np.array([20, 35, 12])
quantities = np.array([100, 40, 250])
revenue = prices @ quantities       # 6400

The result is 2,000 + 1,400 + 3,000 = 6,400 currency units.

For matrices, @ performs matrix multiplication, while remains elementwise multiplication. If A is an m × n matrix and B is an n × k matrix, then AB is m × k*. The inner dimensions must agree.

A linear system can represent resource planning:

Ax = b

Here A contains resource use per product, x contains unknown production quantities, and b contains available resources. If A is square and nonsingular, the unique solution can be computed with np.linalg.solve(A, b).

A = np.array([[2, 1],
              [1, 2]], dtype=float)
b = np.array([100, 80], dtype=float)
x = np.linalg.solve(A, b)

Solving gives x₁ = 40 and x₂ = 20. The equations are 2x₁ + x₂ = 100 and x₁ + 2x₂ = 80. In practice, solve is preferred to explicitly computing np.linalg.inv(A) @ b, because direct solving is generally more numerically stable and efficient. A solution is meaningful only if the model’s units, constraints, and assumptions are valid; negative production quantities may signal that additional business constraints are needed.

Other tools include np.linalg.norm for vector length, np.linalg.det for determinant, and np.linalg.eig for eigenvalue problems. These should be used with awareness of conditioning and numerical precision. A matrix that is nearly singular can produce unstable estimates even when software returns a result.

7. Random Sampling and Simulation

Random numbers support forecasting experiments, risk analysis, A/B-test simulations, and Monte Carlo methods. Modern NumPy recommends a generator object rather than relying on a global random state:

rng = np.random.default_rng(2027)
demand = rng.normal(loc=500, scale=80, size=10000)

The normal distribution is parameterized here by mean 500 units and standard deviation 80 units. A seed makes the generated sequence reproducible; it does not make the model true or eliminate sampling uncertainty.

Common methods include rng.integers for discrete values, rng.choice for sampling categories, rng.uniform for continuous uniform values, and rng.binomial for numbers of successes in repeated trials. For a binomial variable X ~ Binomial(n, p), n is the number of trials and p is the success probability. Its expected value is E(X) = np, and its variance is Var(X) = np(1 − p). These relationships assume independent trials with a constant success probability.

Simulation outputs should be summarized with uncertainty, not just a single simulated number. For example, a 95% empirical interval may be formed from the 2.5th and 97.5th percentiles of simulated outcomes. Such an interval describes the simulation distribution and should not automatically be called a confidence interval for a population parameter.

8. Applied Case: Inventory Risk Simulation

Background and problem

The illustrative company Northstar Retail sells a seasonal item. Daily demand is historically close to a normal distribution with mean 500 units and standard deviation 80 units. Management wants to estimate the probability that demand exceeds available inventory of 650 units during a typical day.

Analysis and approach

A simulation can generate many hypothetical demand values:

rng = np.random.default_rng(7)
demand = rng.normal(500, 80, 200_000)
probability_stockout = np.mean(demand > 650)
expected_shortage = np.mean(np.maximum(demand - 650, 0))

The indicator expression demand > 650 produces Boolean values, whose mean is the proportion of simulated days exceeding 650. The shortage expression is zero when inventory is sufficient and equals excess demand otherwise. Because a normal distribution can generate negative demand, a production model should either justify the approximation or use a nonnegative distribution such as a suitably chosen Poisson, gamma, or empirical model.

The analytical normal approximation provides a useful check. The standardized threshold is:

z = (650 − 500) ÷ 80 = 1.875

The stockout probability is the upper-tail probability above z = 1.875, approximately 3.0% under a normal model. Simulation should produce a value near this, subject to sampling variation.

Outcome and managerial interpretation

If the simulated probability is close to 3%, management can compare the expected cost of stockouts with the cost of holding additional inventory. The result is not a recommendation by itself. Demand estimates may be biased, demand may be autocorrelated, promotions may change the distribution, and supply interruptions may create additional risk.

Learning points

The case demonstrates vectorized comparison, aggregation of Boolean values, elementwise maximum, reproducible random generation, and model validation through an approximate theoretical calculation. It also shows why computational efficiency must be accompanied by careful assumptions.

9. Numerical Reliability, Governance, and Best Practice

Floating-point numbers are approximations. For example, binary representation may prevent a decimal such as 0.1 from being stored exactly. Avoid testing floating-point equality with == when a tolerance is appropriate; use np.isclose or np.allclose and specify tolerances suitable for the application.

Important practices include:

  1. Validate shape, units, and data type before calculation.
  2. Prefer vectorized operations, but prioritize readability over obscure micro-optimizations.
  3. Use np.errstate or explicit checks when division, logarithms, or square roots may encounter invalid values.
  4. Preserve source arrays with .copy() when mutation is possible.
  5. Use stable algorithms and avoid explicit matrix inversion when solving systems.
  6. Set and document random seeds for reproducible analysis, while recognizing that reproducibility is not the same as validity.
  7. Test calculations against small hand-worked examples and known identities.
  8. Record assumptions, missing-data treatment, units, and versions in analytical documentation.

NumPy itself does not understand business meaning, privacy, fairness, or authorization. Analysts remain responsible for protecting personal data, limiting access, and ensuring that simulated or transformed results are not presented as observed facts. In automated decision systems, numerical transformations can amplify bias if historical data reflects unequal treatment. Governance therefore includes technical validation, provenance, review, and clear communication of uncertainty.

10. Contemporary Relevance

NumPy arrays underpin scientific Python and are widely used in machine learning, optimization, forecasting, and risk analytics. Modern systems increasingly use array backends that run on GPUs or distributed hardware, but the fundamental concepts—shape, broadcasting, data type, numerical stability, and reproducibility—remain essential. Analysts should understand whether a library uses NumPy semantics exactly, especially for missing values, random-number generation, and matrix operations.

Chapter Summary

NumPy provides an efficient array model for business analytics. Arrays have dimensions, shapes, sizes, and data types that must be interpreted correctly. Indexing and Boolean masks support targeted selection; vectorization expresses arithmetic over complete datasets; and broadcasting applies compatible row- or column-level values without manual loops. Aggregations summarize business data, but axis choices, missing values, weights, and sample-versus-population definitions affect interpretation. The @ operator and np.linalg.solve support matrix-based revenue models and linear systems, while random generators enable reproducible simulation. Reliable numerical analysis also requires unit checks, floating-point awareness, stable algorithms, documented assumptions, and responsible governance.

Key Terms

Array: An ordered numerical data structure managed by NumPy.

Axis: A dimension along which an array operation is performed.

Broadcasting: NumPy’s rule-based expansion of compatible array shapes.

Boolean mask: An array of true/false values used for selection.

Data type (dtype): The numerical representation used to store array elements.

Dimension (ndim): The number of axes in an array.

Elementwise operation: An operation applied independently to corresponding elements.

Floating-point number: A finite-precision approximation to a real number.

Generator: A NumPy object used to produce reproducible random values.

Inner product: The sum of pairwise products of two compatible vectors.

Matrix multiplication: The product operation combining rows of one matrix with columns of another.

Monte Carlo simulation: Repeated random sampling used to estimate uncertain outcomes.

Shape: The length of an array along each axis.

Standard deviation: A measure of dispersion around a mean.

Ufunc: A NumPy universal function designed for elementwise array operations.

Vectorization: Applying array operations without explicit Python-level iteration.

View: An array object sharing data with another array.

Weighted mean: An average in which observations contribute according to specified weights.

Review Questions

Short Answer Questions

  1. What information is conveyed by an array’s shape and dtype?
  2. Why are parentheses required around individual conditions in a compound NumPy mask?
  3. Distinguish elementwise multiplication from matrix multiplication in NumPy.
  4. What does axis=0 generally mean when aggregating a two-dimensional array?
  5. Why should a random seed be documented in a simulation?

Long Answer Questions

  1. Explain vectorization and broadcasting, and illustrate how they can support monthly sales adjustments.
  2. Compare population and sample standard deviation, including the role of ddof.
  3. Describe how a revenue model can be represented with vectors and matrices.
  4. Explain the main numerical and governance risks in array-based business analysis.
  5. Design a NumPy workflow for estimating inventory stockout risk and justify its assumptions.

Analytical / Application Questions

  1. A company records product sales in a (4, 12) array and has a 12-element monthly inflation-adjustment vector. Write the NumPy operation that applies each monthly factor to all products and explain why broadcasting works.
  2. A portfolio contains returns [0.04, -0.02, 0.06] and investment weights [0.5, 0.3, 0.2]. Explain how to compute the weighted portfolio return and state the condition the weights should satisfy for a fully invested portfolio.
  3. A simulation generates negative demand values from a normal distribution. Analyze why this is problematic and recommend two defensible responses.

Multiple Choice Questions with Answers

Question 1

Which NumPy property reports the number of axes in an array?

A. size

B. shape

C. ndim

D. dtype

Correct Answer: C. ndim

Question 2

For arrays with equal shape, which expression performs elementwise multiplication?

A. a * b

B. a @ b

C. np.solve(a, b)

D. a.T

Correct Answer: A. a * b

Question 3

A sales array has shape (2, 3) and an adjustment array has shape (3,). What does broadcasting do?

A. Raises an error because dimensions differ

B. Applies the three adjustments across each row

C. Applies one adjustment to each row

D. Converts both arrays to scalars

Correct Answer: B. Applies the three adjustments across each row

Question 4

In sales.sum(axis=0) for a two-dimensional product-by-month array, what is returned?

A. One total for every product

B. One total for every month

C. The grand total only

D. The original array unchanged

Correct Answer: B. One total for every month

Question 5

Which NumPy function is generally preferred for solving Ax = b rather than explicitly calculating an inverse?

A. np.linalg.solve

B. np.linalg.det

C. np.linalg.eig

D. np.cumsum

Correct Answer: A. np.linalg.solve

Question 6

Prices are [20, 35, 12] and quantities are [100, 40, 250]. What is total revenue?

A. 3,400

B. 5,600

C. 6,400

D. 7,800

Correct Answer: C. 6,400

Question 7

What does np.mean(demand > 650) estimate when demand is a simulated demand array?

A. The maximum demand

B. The proportion of simulations exceeding 650

C. The average shortage conditional on stockout

D. The standard deviation of demand

Correct Answer: B. The proportion of simulations exceeding 650

Question 8

Which setting gives NumPy’s standard deviation the usual sample denominator n − 1?

A. ddof=0

B. ddof=1

C. axis=1

D. dtype=float

Correct Answer: B. ddof=1

Question 9

Which practice best addresses possible floating-point comparison errors?

A. Always round inputs to integers

B. Use np.isclose with an appropriate tolerance

C. Replace all floats with strings

D. Use matrix inversion

Correct Answer: B. Use np.isclose with an appropriate tolerance

Question 10

If a two-variable linear system is 2x₁ + x₂ = 100 and x₁ + 2x₂ = 80, what is its solution?

A. x₁ = 20, x₂ = 40

B. x₁ = 30, x₂ = 30

C. x₁ = 40, x₂ = 20

D. x₁ = 50, x₂ = 10

Correct Answer: C. x₁ = 40, x₂ = 20

Python for Business Analytics
Chapter 7 of 15