Centroid of a set of N-dimensional points

In analytic geometry, the centroid of a set of points is a point in the same domain as the set. The centroid point is chosen to show a property which can be calculated for that set.

Centroid of a set of N-dimensional points is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Consider the centroid defined as the arithmetic mean of a set of points of arbitrary dimension.

Task

Create a function in your chosen programming language to calculate such a centroid using an arbitrary number of points of arbitrary dimension.

Test your function with the following groups of points
one-dimensional: (1), (2), (3)
two-dimensional: (8, 2), (0, 0)
three-dimensional: the set (5, 5, 0), (10, 10, 0) and the set (1, 3.1, 6.5), (-2, -5, 3.4), (-7, -4, 9), (2, 0, 3)
five-dimensional: (0, 0, 0, 0, 1), (0, 0, 0, 1, 0), (0, 0, 1, 0, 0), (0, 1, 0, 0, 0)


Stretch task
   Show a 3D plot image of the second 3-dimensional set and its centroid.
See Also
[Wikipedia page]
[Wolfram Mathworld on Centroid]


ALGOL 68

BEGIN # find the centroid of some N-dimensional points                       #

    # returns the centroid of points                                         #
    OP   CENTROID = ( [,]REAL points )[]REAL:
         BEGIN
            INT number of points = ( 1 UPB points - 1 LWB points ) + 1;
            [ 2 LWB points : 2 UPB points ]REAL result;
            FOR j FROM 2 LWB points TO 2 UPB points DO
                REAL sum := 0;
                FOR i FROM 1 LWB points TO 1 UPB points DO sum +:= points[ i, j ] OD;
                result[ j ] := sum / number of points
            OD;
            result
         END # CENTROID # ;

    OP   A         = ( INT  v )[]REAL: v;              # coerces v to []REAL #
    OP   FMT       = ( REAL v )STRING:     # formsts v with up to 2 decimals #
         BEGIN
            STRING result := fixed( v, -0, 2 );
            IF result[ LWB result ] = "." THEN "0" +=: result FI;
            WHILE result[ UPB result ] = "0" DO result := result[ : UPB result - 1 ] OD;
            IF result[ UPB result ] = "." THEN result := result[ : UPB result - 1 ] FI;
            " " + result
         END # FMT # ; 
    OP   SHOW      = ( []REAL v )VOID:      # show a 1D array (row) of reals #
         BEGIN
            print( ( "[" ) );
            FOR i FROM LWB v TO UPB v DO print( ( FMT v[ i ] ) ) OD;
            print( ( " ]" ) )
         END # SHOW # ;
    OP   SHOW      = ( [,]REAL v )VOID:           # show a 2D array of reals #
         BEGIN
            print( ( "[" ) );
            FOR i FROM 1 LWB v TO 1 UPB v DO SHOW v[ i, : ] OD;
            print( ( "]" ) )
         END # SHOW # ;

    # task test cases                                                        #

    PROC test = ( [,]REAL points )VOID:         # test the CENTROID operator #
         BEGIN SHOW          points; print( ( "   -> " ) );
               SHOW CENTROID points; print( ( newline ) )
         END # test # ;

    test( ( A(1), A(2), A(3) ) );
    test( ( ( 8, 2 ), ( 0, 0 ) ) );
    test( ( ( 5, 5, 0 ), ( 10, 10, 0 ) ) );
    test( ( (  1, 3.1, 6.5 ), ( -2, -5, 3.4 )
          , ( -7,  -4, 9   ), (  2,  0,   3 )
          )
        );
    test( ( ( 0, 0, 0, 0, 1 ), ( 0, 0, 0, 1, 0 )
          , ( 0, 0, 1, 0, 0 ), ( 0, 1, 0, 0, 0 )
          )
        )

END
Output:
[[ 1 ][ 2 ][ 3 ]]   -> [ 2 ]
[[ 8 2 ][ 0 0 ]]   -> [ 4 1 ]
[[ 5 5 0 ][ 10 10 0 ]]   -> [ 7.5 7.5 0 ]
[[ 1 3.1 6.5 ][ -2 -5 3.4 ][ -7 -4 9 ][ 2 0 3 ]]   -> [ -1.5 -1.47 5.47 ]
[[ 0 0 0 0 1 ][ 0 0 0 1 0 ][ 0 0 1 0 0 ][ 0 1 0 0 0 ]]   -> [ 0 0.25 0.25 0.25 0.25 ]

ALGOL W

begin % find the centroid of some N dimensional points                       %

    % sets cPoints to the centroid of points                                 %
    procedure centroid( real array points ( *, * )
                      ; integer value numberOfPoints, dimension
                      ; real array cPoint ( * )
                      ) ;
    for j := 1 until dimension do begin
        real sum;
        sum := 0;
        for i := 1 until numberOfpoints do sum := sum + points( i, j );
        cPoint( j ) := sum / numberOfPoints
    end centroid ;

    begin % task test cases                                                  %

        % show a real number with two decimal places if if is not integral   %
        procedure show ( real value rValue ) ;
        begin
            integer iValue;
            iValue := truncate( rValue );
            if iValue = rValue then writeon( s_w := 0, i_w := 1, " ", iValue )
                               else writeon( s_w := 0
                                           , r_format := "A", r_w := 4, r_d := 2
                                           , " ", rValue
                                           )
        end show ;

        procedure testCentroid( real array points ( *, * )
                              ; integer value numberOfPoints, dimension
                              ) ;
        begin
            real array cPoint( 1 :: dimension );
            centroid( points, numberOfPoints, dimension, cPoint );
            write( "[" );
            for i := 1 until numberOfPoints do begin
                writeon( "[" );
                for j := 1 until dimension do show( points( i, j ) );
                writeon( " ]" );
            end for_i ;
            writeon( "] -> [" );
            for j := 1 until dimension do show( cPoint( j ) );
            writeon( " ]" )
        end testCentroid ;

        real array p1 ( 1 :: 3, 1 :: 1 ); real array p2 ( 1 :: 2, 1 :: 2 );
        real array p3 ( 1 :: 2, 1 :: 3 ); real array p4 ( 1 :: 4, 1 :: 3 );
        real array p5 ( 1 :: 4, 1 :: 5 );

        p1( 1, 1 ) :=  1;  p1( 2, 1 ) :=   2; p1( 3, 1 ) := 3; 
        p2( 1, 1 ) :=  8;  p2( 1, 2 ) :=   2; 
        p2( 2, 1 ) :=  0;  p2( 2, 2 ) :=   0; 
        p3( 1, 1 ) :=  5;  p3( 1, 2 ) :=   5; p3( 1, 3 ) := 0; 
        p3( 2, 1 ) := 10;  p3( 2, 2 ) :=  10; p3( 2, 3 ) := 0; 
        p4( 1, 1 ) :=  1;  p4( 1, 2 ) := 3.1; p4( 1, 3 ) := 6.5; 
        p4( 2, 1 ) := -2;  p4( 2, 2 ) :=  -5; p4( 2, 3 ) := 3.4; 
        p4( 3, 1 ) := -7;  p4( 3, 2 ) :=  -4; p4( 3, 3 ) := 9; 
        p4( 4, 1 ) :=  2;  p4( 4, 2 ) :=   0; p4( 4, 3 ) := 3; 
        p5( 1, 1 ) :=  0;  p5( 1, 2 ) :=   0; p5( 1, 3 ) := 0;  p5( 1, 4 ) := 0;  p5( 1, 5 ) := 1; 
        p5( 2, 1 ) :=  0;  p5( 2, 2 ) :=   0; p5( 2, 3 ) := 0;  p5( 2, 4 ) := 1;  p5( 2, 5 ) := 0; 
        p5( 3, 1 ) :=  0;  p5( 3, 2 ) :=   0; p5( 3, 3 ) := 1;  p5( 3, 4 ) := 0;  p5( 3, 5 ) := 0; 
        p5( 4, 1 ) :=  0;  p5( 4, 2 ) :=   1; p5( 4, 3 ) := 0;  p5( 4, 4 ) := 0;  p5( 4, 5 ) := 0; 

        testCentroid( p1, 3, 1 );
        testCentroid( p2, 2, 2 );
        testCentroid( p3, 2, 3 );
        testCentroid( p4, 4, 3 );
        testCentroid( p5, 4, 5 )

    end

end.
Output:
[[ 1 ][ 2 ][ 3 ]] -> [ 2 ]
[[ 8 2 ][ 0 0 ]] -> [ 4 1 ]
[[ 5 5 0 ][ 10 10 0 ]] -> [ 7.50 7.50 0 ]
[[ 1 3.10 6.50 ][ -2 -5 3.40 ][ -7 -4 9 ][ 2 0 3 ]] -> [ -1.50 -1.47 5.47 ]
[[ 0 0 0 0 1 ][ 0 0 0 1 0 ][ 0 0 1 0 0 ][ 0 1 0 0 0 ]] -> [ 0 0.25 0.25 0.25 0.25 ]

BASIC256

Translation of: FreeBASIC
subroutine Centroid(n, d, pts)
	dim ctr(d)

	for j = 0 to d-1
		ctr[j] = 0
		for i = 0 to n-1
			ctr[j] += pts[i,j]
		next
		ctr[j] /= n
	next
	print "{";
	for i = 0 to n-1
		print "{";
		for j = 0 to d-1
			print pts[i,j];
			if j < d-1 then print ", ";
		next
		print "}";
		if i < n-1 then print ", ";
	next
	print "} => Centroid: {";
	for j = 0 to d-1
		print ctr[j];
		if j < d-1 then print ", ";
	next
	print "}"
end subroutine

pts1 = {{1}, {2}, {3}}
pts2 = {{8, 2}, {0, 0}}
pts3 = {{5, 5, 0}, {10, 10, 0}}
pts4 = {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}}
pts5 = {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}}

call Centroid(3, 1, pts1)
call Centroid(2, 2, pts2)
call Centroid(2, 3, pts3)
call Centroid(4, 3, pts4)
call Centroid(4, 5, pts5)
end
Output:
Similar to FreeBASIC entry.

C

The image will, of course, be the same as Wren and Go when the relevant points are fed into Gnuplot.

#include <stdio.h>

void centroid(int n, int d, double pts[n][d]) {
    int i, j;
    double ctr[d];
    for (j = 0; j < d; ++j) {
        ctr[j] = 0.0;
        for (i = 0; i < n; ++i) {
            ctr[j] += pts[i][j];
        }
        ctr[j] /= n;
    }
    printf("{");
    for (i = 0; i < n; ++i) {
        printf("{");
        for (j = 0; j < d; ++j) {
            printf("%g", pts[i][j]);
            if (j < d -1) printf(", ");
        }
        printf("}");
        if (i < n - 1) printf(", ");
    }
    printf("} => Centroid: {");
    for (j = 0; j < d; ++j) {
        printf("%g", ctr[j]);
        if (j < d-1) printf(", ");
    }
    printf("}\n");
}

int main() {
    double pts1[3][1] = { {1}, {2}, {3} };
    double pts2[2][2] = { {8, 2}, {0, 0} };
    double pts3[2][3] = { {5, 5, 0}, {10, 10, 0} };
    double pts4[4][3] = { {1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3} };
    double pts5[4][5] = { {0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0} };

    centroid(3, 1, pts1);
    centroid(2, 2, pts2);
    centroid(2, 3, pts3);
    centroid(4, 3, pts4);
    centroid(4, 5, pts5);
    return 0;
}
Output:
{{1}, {2}, {3}} => Centroid: {2}
{{8, 2}, {0, 0}} => Centroid: {4, 1}
{{5, 5, 0}, {10, 10, 0}} => Centroid: {7.5, 7.5, 0}
{{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}} => Centroid: {-1.5, -1.475, 5.475}
{{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}} => Centroid: {0, 0.25, 0.25, 0.25, 0.25}

C++

#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <vector>

template <typename T>
void print_vector(const std::vector<T>& vec) {
	std::cout << "[";
	for ( uint32_t i = 0; i < vec.size() - 1; ++i ) {
		std::cout << vec[i] << ", ";
	}
	std::cout << vec.back() << "]";
}

template <typename T>
void print_2D_vector(const std::vector<std::vector<T>>& vecs) {
	std::cout << "[";
	for ( uint32_t i = 0; i < vecs.size() - 1; ++i ) {
		print_vector(vecs[i]); std::cout << ", ";
	}
	print_vector(vecs.back()); std::cout << "]";
}

bool all_same_size(const std::vector<std::vector<double>>& points, const uint32_t& dimension) {
	for ( uint32_t i = 1; i < points.size(); ++i ) {
		if ( points[i].size() != dimension ) {
			return false;
		}
	}
	return true;
}

std::vector<double> centroid(const std::vector<std::vector<double>>& points) {
	if ( points.empty() ) {
		throw std::invalid_argument("Vector must contain at least one point.");
	}

	const uint32_t dimension = points[0].size();
	if ( ! all_same_size(points, dimension) ) {
		throw std::invalid_argument("Points must all have the same dimension.");
	}

	std::vector<double> result(dimension, 0.0);
	for ( uint32_t j = 0; j < dimension; ++j ) {
		for ( uint32_t i = 0; i < points.size(); ++i ) {
			result[j] += points[i][j];
		}
		result[j] /= points.size();
	}
	return result;
}

int main() {
	std::vector<std::vector<std::vector<double>>> vector_points = {
		std::vector{ std::vector{ 1.0 }, std::vector{ 2.0 }, std::vector{ 3.0 } },
		std::vector{ std::vector{ 8.0, 2.0 }, std::vector{ 0.0, 0.0 } },
		std::vector{ std::vector{ 5.0, 5.0, 0.0 }, std::vector{ 10.0, 10.0, 0.0 } },
		std::vector{ std::vector{ 1.0, 3.1, 6.5 }, std::vector{ -2.0, -5.0, 3.4 },
				     std::vector{ -7.0, -4.0, 9.0 }, std::vector{ 2.0, 0.0, 3.0 } },
		std::vector{ std::vector{ 0.0, 0.0, 0.0, 0.0, 1.0 }, std::vector{ 0.0, 0.0, 0.0, 1.0, 0.0 },
				     std::vector{ 0.0, 0.0, 1.0, 0.0, 0.0 }, std::vector{ 0.0, 1.0, 0.0, 0.0, 0.0 } }
	};

	for ( const std::vector<std::vector<double>>& points : vector_points ) {
		print_2D_vector(points); std::cout << " => Centroid: "; print_vector(centroid(points)); std::cout << std::endl;
	}
}
Output:
[[1], [2], [3]] => Centroid: [2]
[[8, 2], [0, 0]] => Centroid: [4, 1]
[[5, 5, 0], [10, 10, 0]] => Centroid: [7.5, 7.5, 0]
[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]] => Centroid: [-1.5, -1.475, 5.475]
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Centroid: [0, 0.25, 0.25, 0.25, 0.25]

DuckDB

Works with: DuckDB version V1.1
# Note: this implementation of transpose() is potentially lossy for jagged inputs
create or replace function transpose(lst) as
  select list_transform( range(1, 1+length(lst[1])),
                         j -> list_transform(range(1, length(lst)+1),
                                              i -> lst[i][j]) );

create or replace function centroid(points) as ( 
  if ( length(points) = 0,
     error('centroid: list must contain at least one point.'),
     if (length(list_filter(points, x -> length(x) != length(points[1]))) > 0,
        error('centroid: points must all have the same dimension.'),
        transpose(points)
        .list_transform(x -> list_sum(x) / length(points))))
);

## Examples
select points, centroid(points)
from unnest([
  [[1], [2], [3]],
  [[8, 2], [0, 0]],
  [[5, 5, 0], [10, 10, 0]],
  [[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]],
  [[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]]
  ]) _(points);
Output:

(elided)

┌───────────────────────────────────────────────────────────────────────┬───────────────────────────────┐
│                                points                                 │       centroid(points)        │
│                           decimal(11,1)[][]                           │           double[]            │
├───────────────────────────────────────────────────────────────────────┼───────────────────────────────┤
│ [[1.0], [2.0], [3.0]]                                                 │ [2.0]                         │
│ [[8.0, 2.0], [0.0, 0.0]]                                              │ [4.0, 1.0]                    │
│ [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]]                                  │ [7.5, 7.5, 0.0]               │
│ [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, …  │ [-1.5, -1.475, 5.475]         │
│ [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1…  │ [0.0, 0.25, 0.25, 0.25, 0.25] │
└───────────────────────────────────────────────────────────────────────┴───────────────────────────────┘

FreeBASIC

Translation of: XPL0
Sub Centroid(n As Ubyte, d As Ubyte, pts() As Single)
    Dim As Ubyte i, j
    Dim As Single ctr(d)
    
    For j = 0 To d-1
        ctr(j) = 0
        For i = 0 To n-1
            ctr(j)+ = pts(i,j)
        Next
        ctr(j) /= n
    Next
    Print "{"; 
    For i = 0 To n-1
        Print "{";
        For j = 0 To d-1
            Print Using "&"; pts(i,j);
            If j < d-1 Then Print ", ";
        Next
        Print "}";
        If i < n-1 Then Print ", ";
    Next
    Print "} => Centroid: {";
    For j = 0 To d-1
        Print Using "&"; ctr(j);
        If j < d-1 Then Print ", ";
    Next
    Print "}"
End Sub

Dim pts1(2, 1) As Single = {{1}, {2}, {3}}
Dim pts2(1, 1) As Single = {{8, 2}, {0, 0}}
Dim pts3(1, 2) As Single = {{5, 5, 0}, {10, 10, 0}}
Dim pts4(3, 2) As Single = {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}}
Dim pts5(3, 4) As Single = {{0, 0, 0, 0, 1}, _
{0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}}

Centroid(3, 1, pts1())
Centroid(2, 2, pts2())
Centroid(2, 3, pts3())
Centroid(4, 3, pts4())
Centroid(4, 5, pts5())

Sleep
Output:
{{1}, {2}, {3}} => Centroid: {2}
{{8, 2}, {0, 0}} => Centroid: {4, 1}
{{5, 5, 0}, {10, 10, 0}} => Centroid: {7.5, 7.5, 0}
{{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}} => Centroid: {-1.5, -1.475, 5.475}
{{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}} => Centroid: {0, 0.25, 0.25, 0.25, 0.25}

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

Definition of function

 

Test cases:

 

 

Go

Translation of: Wren

The image will, of course, be the same as Wren when the relevant points are fed into Gnuplot.

package main

import (
    "fmt"
    "log"
)

func centroid(pts [][]float64) []float64 {
    n := len(pts)
    if n == 0 {
        log.Fatal("Slice must contain at least one point.")
    }
    d := len(pts[0])
    for i := 1; i < n; i++ {
        if len(pts[i]) != d {
            log.Fatal("Points must all have the same dimension.")
        }
    }
    res := make([]float64, d)
    for j := 0; j < d; j++ {
        for i := 0; i < n; i++ {
            res[j] += pts[i][j]
        }
        res[j] /= float64(n)
    }
    return res
}

func main() {
    points := [][][]float64{
        {{1}, {2}, {3}},
        {{8, 2}, {0, 0}},
        {{5, 5, 0}, {10, 10, 0}},
        {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}},
        {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}},
    }
    for _, pts := range points {
        fmt.Println(pts, "=> Centroid:", centroid(pts))
    }
}
Output:
[[1] [2] [3]] => Centroid: [2]
[[8 2] [0 0]] => Centroid: [4 1]
[[5 5 0] [10 10 0]] => Centroid: [7.5 7.5 0]
[[1 3.1 6.5] [-2 -5 3.4] [-7 -4 9] [2 0 3]] => Centroid: [-1.5 -1.475 5.475]
[[0 0 0 0 1] [0 0 0 1 0] [0 0 1 0 0] [0 1 0 0 0]] => Centroid: [0 0.25 0.25 0.25 0.25]

Java

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class CentroidOfASetOfNDimensionalPoints {

	public static void main(String[] args) {
		List<List<List<Double>>> listPoints = List.of(
		    List.of( List.of( 1.0 ), List.of( 2.0 ), List.of( 3.0 ) ),
		    List.of( List.of( 8.0, 2.0 ), List.of( 0.0, 0.0 ) ),
		    List.of( List.of( 5.0, 5.0, 0.0 ), List.of( 10.0, 10.0, 0.0 ) ),
		    List.of( List.of( 1.0, 3.1, 6.5 ), List.of( -2.0, -5.0, 3.4 ),
		    		 List.of( -7.0, -4.0, 9.0 ), List.of( 2.0, 0.0, 3.0 ) ),
		    List.of( List.of( 0.0, 0.0, 0.0, 0.0, 1.0 ), List.of( 0.0, 0.0, 0.0, 1.0, 0.0 ),
		    		 List.of( 0.0, 0.0, 1.0, 0.0, 0.0 ), List.of( 0.0, 1.0, 0.0, 0.0, 0.0 ) )
		);
		
		listPoints.forEach( points -> {
		    System.out.println(points + " => Centroid: " + centroid(points));
		} );
	}
	
	private static List<Double> centroid(List<List<Double>> points) {
		if ( points.isEmpty() ) {
			throw new AssertionError("List must contain at least one point.");
		}
		
	    final int dimension = points.getFirst().size();
	    if ( ! points.stream().skip(1).allMatch( list -> list.size() == dimension ) ) {
	        throw new AssertionError("Points must all have the same dimension.");
	    }
	    
		List<Double> result = Stream.generate( () -> 0.0 ).limit(dimension).collect(Collectors.toList());
	    for ( int j = 0; j < dimension; j++ ) {
	        for ( int i = 0; i < points.size(); i++ ) {
	        	result.set(j, result.get(j) + points.get(i).get(j));
	        }
	        result.set(j, result.get(j) / points.size());
	    }
	    return result;
	}

}
Output:
[[1.0], [2.0], [3.0]] => Centroid: [2.0]
[[8.0, 2.0], [0.0, 0.0]] => Centroid: [4.0, 1.0]
[[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]] => Centroid: [7.5, 7.5, 0.0]
[[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]] => Centroid: [-1.5, -1.475, 5.475]
[[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]]
 => Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]

jq

Works with: jq

Also works with gojq, the Go implementation of jq.

With a trivial change to the last line (the one using string interpolation), the following also works with jaq, the Rust implementation of jq.

# Input: an array of points of the same dimension (i.e. numeric arrays of the same length)
def centroid:
  length as $n
  | if ($n == 0) then "centroid: list must contain at least one point." | error else . end
  | (.[0]|length) as $d
  | if any( .[]; length != $d ) 
    then "centroid: points must all have the same dimension." | error
    else .
    end
  | transpose
  | map( add / $n ) ;

def points: [
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ]
 ];

points[]
| "\(.) => Centroid: \(centroid)"
Output:

Essentially as for Wren.

Julia

using Plots

struct Point{T, N}
    v::Vector{T}
end

function centroid(points::Vector{Point{T, N}}) where N where T
    arr = zeros(T, N)
    for p in points, (i, x) in enumerate(p.v)
        arr[i] += x
    end
    return Point{T, N}(arr / length(points))
end

function centroid(arr)
    isempty(arr) && return Point{Float64, 0}(arr)
    n = length(arr[begin])
    t = typeof(arr[begin][begin])
    return centroid([Point{t, n}(v) for v in arr])
end

const testvecs = [[[1], [2], [3]],
                  [(8, 2), (0, 0)],
                  [[5, 5, 0], [10, 10, 0]],
                  [[1.0, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9.0], [2.0, 0.0, 3.0],],
                  [[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0],],
                 ]

function test_centroids(tests)
    for t in tests
        isempty(t) && error("The empty set of points $t has no centroid")
        vvec = [Point{Float64, length(t[begin])}(collect(v)) for v in t]
        println("$t => $(centroid(vvec))")
    end
    xyz = [p[1] for p in tests[4]], [p[2] for p in tests[4]], [p[3] for p in tests[4]]
    cpoint = centroid(tests[4]).v
    for i in eachindex(cpoint)
        push!(xyz[i], cpoint[i])
    end
    scatter(xyz..., color = [:navy, :navy, :navy, :navy, :red], legend = :none)
end

test_centroids(testvecs)
Output:
[[1], [2], [3]] => Point{Float64, 1}([2.0])
[(8, 2), (0, 0)] => Point{Float64, 2}([4.0, 1.0])
[[5, 5, 0], [10, 10, 0]] => Point{Float64, 3}([7.5, 7.5, 0.0])
[[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]] => Point{Float64, 3}([-1.5, -1.475, 5.475])
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Point{Float64, 5}([0.0, 0.25, 0.25, 0.25, 0.25])
 

Lua

Based on the Algol 68 sample.

do -- find the centroid of some N-dimensional points

    function centroid( points ) -- returns the centroid of points
         local result = {}
         if #points > 0 then
             for j = 1, #points[ 1 ] do
                 local sum = 0
                 for i = 1, #points do sum = sum + points[ i ][ j ] end
                 result[ j ] = sum / #points
             end
         end
         return result
    end 

    function show1d( v ) -- show a 1D array of floats
        io.write( "[" )
        for i = 1, #v do io.write( " ", v[ i ] ) end
        io.write( " ]" )
    end 
    function show2d( v ) -- show a 2D array of floats
        io.write( "[" )
        for i = 1 , #v do show1d( v[ i ] ) end
        io.write( "]" )
    end 

    -- task test cases

    function testCentroid( points )
         show2d( points )
         io.write( "   -> " )
         show1d( centroid( points ) )
         io.write( "\n" )
    end

    testCentroid{ { 1 }, { 2 }, { 3 } }
    testCentroid{ { 8, 2 }, { 0, 0 } }
    testCentroid{ { 5, 5, 0 }, { 10, 10, 0 } }
    testCentroid{ {  1, 3.1, 6.5 }, { -2, -5, 3.4 }
                , { -7,  -4, 9   }, {  2,  0,   3 }
                }
    testCentroid{ { 0, 0, 0, 0, 1 }, { 0, 0, 0, 1, 0 }
                , { 0, 0, 1, 0, 0 }, { 0, 1, 0, 0, 0 }
                }
end
Output:
[[ 1 ][ 2 ][ 3 ]]   -> [ 2 ]
[[ 8 2 ][ 0 0 ]]   -> [ 4 1 ]
[[ 5 5 0 ][ 10 10 0 ]]   -> [ 7.5 7.5 0 ]
[[ 1 3.1 6.5 ][ -2 -5 3.4 ][ -7 -4 9 ][ 2 0 3 ]]   -> [ -1.5 -1.475 5.475 ]
[[ 0 0 0 0 1 ][ 0 0 0 1 0 ][ 0 0 1 0 0 ][ 0 1 0 0 0 ]]   -> [ 0 0.25 0.25 0.25 0.25 ]

Nim

type Coords[N: static Positive] = array[N, float]


proc centroid(points: openArray[Coords]): Coords =
  ## Return the coordinates of the centroid of the given points.
  for point in points:
    for i, coord in point:
      result[i] += coord
  for coord in result.mitems:
    coord /= points.len.toFloat


proc displayCentroid(points: openArray[Coords]) =
  echo "Set: ", points
  echo "Centroid: ", points.centroid
  echo()


const
  Points1: seq[Coords[1]] = @[[1], [2], [3]]
  Points2: seq[Coords[2]] = @[[8, 2], [0, 0]]
  Points3: seq[Coords[3]] = @[[5, 5, 0], [10, 10, 0]]
  Points4: seq[Coords[3]] = @[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]]
  Points5: seq[Coords[5]] = @[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]]

Points1.displayCentroid()
Points2.displayCentroid()
Points3.displayCentroid()
Points4.displayCentroid()
Points5.displayCentroid()
Output:
Set: [[1.0], [2.0], [3.0]]
Centroid: [2.0]

Set: [[8.0, 2.0], [0.0, 0.0]]
Centroid: [4.0, 1.0]

Set: [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]]
Centroid: [7.5, 7.5, 0.0]

Set: [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]]
Centroid: [-1.5, -1.475, 5.475]

Set: [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]]
Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]

Perl

PDL library, with plot

Library: PDL
use v5.36;
use PDL;

sub centroid ($LoL) {
    return pdl($LoL)->transpose->average;
}

sub plot_with_centroid ($LoL) {
    require PDL::Graphics::Gnuplot;
    my $p   = pdl($LoL);
    my $pc  = $p->glue(1, centroid($p));
    my @xyz = map { $pc->slice("($_)") } 0..2;

    my $colors = [8,8,8,8,7];

    PDL::Graphics::Gnuplot->new('png')->plot3d(
        square => 1,
        grid   => [qw<xtics ytics ztics>],
        { with => 'points', pt => 7, ps => 2, linecolor => 'variable', },
        @xyz, $colors,
    );
}

my @tests = (
    [ [1,], [2,], [3,] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ],
);
say centroid($_) for @tests;
plot_with_centroid($tests[3]);
Output:
[2]
[4 1]
[7.5 7.5 0]
[-1.5 -1.475 5.475]
[0 0.25 0.25 0.25 0.25]
 

Direct calculation

use v5.36;

sub centroid ($LoL) {
    my $n = $#{ $LoL       };
    my $d = $#{ $LoL->@[0] };
    my @C = 0 x ($d+1);
    for my $i (0..$d) {
        $C[$i] += $LoL->@[$_]->@[$i] for 0..$n;
        $C[$i] /= $n+1
    }
    @C
}

say join ' ', centroid($_) for
    [ [1,], [2,], [3,] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ];
Output:
2
4 1
7.5 7.5 0
-1.5 -1.475 5.475
0 0.25 0.25 0.25 0.25

Phix

with javascript_semantics
function centroid(sequence pts)
    return apply(columnize(pts),average)
end function

constant points = {{{1}, {2}, {3}},
                   {{8, 2}, {0, 0}},
                   {{5, 5, 0}, {10, 10, 0}},
                   {{1, 3.1, 6.5}, {-2, -5, 3.4}, {-7, -4, 9}, {2, 0, 3}},
                   {{0, 0, 0, 0, 1}, {0, 0, 0, 1, 0}, {0, 0, 1, 0, 0}, {0, 1, 0, 0, 0}}}
for p in points do
    printf(1,"%v ==> Centroid: %v\n",{p,centroid(p)})
end for
Output:
{{1},{2},{3}} ==> Centroid: {2}
{{8,2},{0,0}} ==> Centroid: {4,1}
{{5,5,0},{10,10,0}} ==> Centroid: {7.5,7.5,0}
{{1,3.1,6.5},{-2,-5,3.4},{-7,-4,9},{2,0,3}} ==> Centroid: {-1.5,-1.475,5.475}
{{0,0,0,0,1},{0,0,0,1,0},{0,0,1,0,0},{0,1,0,0,0}} ==> Centroid: {0,0.25,0.25,0.25,0.25}

Python

from typing import List

def centroid(points: List[List[float]]) -> List[float]:
    """
    Calculate the centroid (geometric center) of a set of points in n-dimensional space.
    
    Args:
        points: A list of points, where each point is a list of float coordinates.
                All points must have the same dimensionality.
    
    Returns:
        A list representing the centroid point coordinates.
    
    Raises:
        ValueError: If the input list is empty or if points have inconsistent dimensions.
    """
    # Check if the input list is empty
    if not points:
        raise ValueError("List must contain at least one point")
    
    # Determine the number of dimensions
    dimension = len(points[0])
    if not all(len(l) == dimension for l in points):
        raise ValueError("Points must all have the same dimension")
    
    result = [0.0] * dimension  # Initialise centroid coordinates with zeros
    for i in range(dimension):
        for j in range(len(points)):
            result[i] += points[j][i]  # Sum up corresponding coordinates
        result[i] /= len(points)  # Compute the average for each coordinate
    
    return result

points: List[List[List[float]]] = [
    [[1.0], [2.0], [3.0]],
    [[8.0, 2.0], [0.0, 0.0]],
    [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]],
    [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]],
    [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]],
]

for point in points:
    print(f"\nSet: {point}\nCentroid: {centroid(point)}")
Set: [[1.0], [2.0], [3.0]]
Centroid: [2.0]

Set: [[8.0, 2.0], [0.0, 0.0]]
Centroid: [4.0, 1.0]

Set: [[5.0, 5.0, 0.0], [10.0, 10.0, 0.0]]
Centroid: [7.5, 7.5, 0.0]

Set: [[1.0, 3.1, 6.5], [-2.0, -5.0, 3.4], [-7.0, -4.0, 9.0], [2.0, 0.0, 3.0]]
Centroid: [-1.5, -1.475, 5.475]

Set: [[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0]]
Centroid: [0.0, 0.25, 0.25, 0.25, 0.25]

Raku

sub centroid { ( [»+«] @^LoL ) »/» +@^LoL }

say .&centroid for
    ( (1,), (2,), (3,) ),
    ( (8, 2), (0, 0) ),
    ( (5, 5, 0), (10, 10, 0) ),
    ( (1, 3.1, 6.5), (-2, -5, 3.4), (-7, -4, 9), (2, 0, 3) ),
    ( (0, 0, 0, 0, 1), (0, 0, 0, 1, 0), (0, 0, 1, 0, 0), (0, 1, 0, 0, 0) ),
;
Output:
(2)
(4 1)
(7.5 7.5 0)
(-1.5 -1.475 5.475)
(0 0.25 0.25 0.25 0.25)

Sidef

func centroid(l) {
    l.combine {|*a| a.sum } »/» l.len
}

[
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ],
].each {
    say centroid(_)
}
Output:
[2]
[4, 1]
[15/2, 15/2, 0]
[-3/2, -59/40, 219/40]
[0, 1/4, 1/4, 1/4, 1/4]

Wren

var centroid = Fn.new { |pts|
    var n = pts.count
    if (n == 0) Fiber.abort("List must contain at least one point.")
    var d = pts[0].count
    if (pts.skip(1).any { |p| p.count != d }) {
        Fiber.abort("Points must all have the same dimension.")
    }
    var res = List.filled(d, 0)
    for (j in 0...d) {
        for (i in 0...n) {
            res[j] = res[j] + pts[i][j]
        }
        res[j] = res[j] / n
    }
    return res
}

var points = [
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ]
]

for (pts in points) {
    System.print("%(pts) => Centroid: %(centroid.call(pts))")
}
Output:
[[1], [2], [3]] => Centroid: [2]
[[8, 2], [0, 0]] => Centroid: [4, 1]
[[5, 5, 0], [10, 10, 0]] => Centroid: [7.5, 7.5, 0]
[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]] => Centroid: [-1.5, -1.475, 5.475]
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Centroid: [0, 0.25, 0.25, 0.25, 0.25]
Library: Wren-seq
Library: Wren-math

Or, more concise using library methods - output identical.

import "./seq" for Lst
import "./math" for Nums

var centroid = Fn.new { |pts|
    return Lst.columns(pts).map { |c| Nums.mean(c) }.toList
}

var points = [
    [ [1], [2], [3] ],
    [ [8, 2], [0, 0] ],
    [ [5, 5, 0], [10, 10, 0] ],
    [ [1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3] ],
    [ [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0] ]
]

for (pts in points) {
    System.print("%(pts) => Centroid: %(centroid.call(pts))")
}
 

XPL0

Translation of: C
include xpllib;         \for Print

proc Centroid(N, D, Pts);
int  N, D;  real Pts;
int  I, J;
real Ctr;
[Ctr:= RlRes(D);
for J:= 0 to D-1 do
    [Ctr(J):= 0.0;
    for I:= 0 to N-1 do
        Ctr(J):= Ctr(J) + Pts(I,J);
    Ctr(J):= Ctr(J) / float(N);
    ];
Print("[");
for I:= 0 to N-1 do
    [Print("[");
    for J:= 0 to D-1 do
        [Print("%g", Pts(I,J));
        if J < D-1 then Print(", ");
        ];
    Print("]");
    if I < N-1 then Print(", ");
    ];
Print("] => Centroid: [");
for J:= 0 to D-1 do
    [Print("%g", Ctr(J));
    if J < D-1 then Print(", ");
    ];
Print("]\n");
];

real Pts1, Pts2, Pts3, Pts4, Pts5;
[Pts1:= [ [1.], [2.], [3.] ];
 Pts2:= [ [8., 2.], [0., 0.] ];
 Pts3:= [ [5., 5., 0.], [10., 10., 0.] ];
 Pts4:= [ [1., 3.1, 6.5], [-2., -5., 3.4], [-7., -4., 9.], [2., 0., 3.] ];
 Pts5:= [ [0., 0., 0., 0., 1.], [0., 0., 0., 1., 0.], [0., 0., 1., 0., 0.],
          [0., 1., 0., 0., 0.] ];
Centroid(3, 1, Pts1);
Centroid(2, 2, Pts2);
Centroid(2, 3, Pts3);
Centroid(4, 3, Pts4);
Centroid(4, 5, Pts5);
]
Output:
[[1], [2], [3]] => Centroid: [2]
[[8, 2], [0, 0]] => Centroid: [4, 1]
[[5, 5, 0], [10, 10, 0]] => Centroid: [7.5, 7.5, 0]
[[1, 3.1, 6.5], [-2, -5, 3.4], [-7, -4, 9], [2, 0, 3]] => Centroid: [-1.5, -1.475, 5.475]
[[0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0]] => Centroid: [0, 0.25, 0.25, 0.25, 0.25]