GitHub

groupBy()

Group array items by a provided function or key for data organization and analysis.

Utility Function
Arrays
Grouping
Syntax
groupBy(array, keyFn)
Parameters

array
Array

The array to group.

keyFn
Function|string

Function that returns the grouping key, or string property name.

Returns

Object
- Object with grouped arrays as values.

Examples

Basic Grouping by Property

const users = [
  { name: "Alice", department: "Engineering", age: 28 },
  { name: "Bob", department: "Marketing", age: 32 },
  { name: "Charlie", department: "Engineering", age: 25 },
  { name: "Diana", department: "Sales", age: 29 },
  { name: "Eve", department: "Marketing", age: 31 }
];

// Group by department using property name
const byDepartment = groupBy(users, 'department');
console.log(byDepartment);
// {
//   Engineering: [
//     { name: "Alice", department: "Engineering", age: 28 },
//     { name: "Charlie", department: "Engineering", age: 25 }
//   ],
//   Marketing: [
//     { name: "Bob", department: "Marketing", age: 32 },
//     { name: "Eve", department: "Marketing", age: 31 }
//   ],
//   Sales: [
//     { name: "Diana", department: "Sales", age: 29 }
//   ]
// }

Grouping with Custom Function

const products = [
  { name: "Laptop", price: 999, category: "Electronics" },
  { name: "Book", price: 15, category: "Education" },
  { name: "Phone", price: 699, category: "Electronics" },
  { name: "Tablet", price: 299, category: "Electronics" },
  { name: "Notebook", price: 5, category: "Education" }
];

// Group by price range
const byPriceRange = groupBy(products, (product) => {
  if (product.price < 50) return "Budget";
  if (product.price < 500) return "Mid-range";
  return "Premium";
});

console.log(byPriceRange);
// {
//   Budget: [
//     { name: "Book", price: 15, category: "Education" },
//     { name: "Notebook", price: 5, category: "Education" }
//   ],
//   "Mid-range": [
//     { name: "Tablet", price: 299, category: "Electronics" }
//   ],
//   Premium: [
//     { name: "Laptop", price: 999, category: "Electronics" },
//     { name: "Phone", price: 699, category: "Electronics" }
//   ]
// }

Grouping by Multiple Criteria

const orders = [
  { id: 1, status: "pending", priority: "high", region: "US" },
  { id: 2, status: "completed", priority: "low", region: "EU" },
  { id: 3, status: "pending", priority: "high", region: "EU" },
  { id: 4, status: "processing", priority: "medium", region: "US" },
  { id: 5, status: "completed", priority: "high", region: "US" }
];

// Group by status and priority combination
const byStatusPriority = groupBy(orders, (order) => 
  `${order.status}-${order.priority}`
);

console.log(byStatusPriority);
// {
//   "pending-high": [
//     { id: 1, status: "pending", priority: "high", region: "US" },
//     { id: 3, status: "pending", priority: "high", region: "EU" }
//   ],
//   "completed-low": [
//     { id: 2, status: "completed", priority: "low", region: "EU" }
//   ],
//   "processing-medium": [
//     { id: 4, status: "processing", priority: "medium", region: "US" }
//   ],
//   "completed-high": [
//     { id: 5, status: "completed", priority: "high", region: "US" }
//   ]
// }

Grouping by Date Periods

const events = [
  { name: "Meeting A", date: "2023-01-15", type: "meeting" },
  { name: "Conference", date: "2023-01-20", type: "conference" },
  { name: "Meeting B", date: "2023-02-05", type: "meeting" },
  { name: "Workshop", date: "2023-02-10", type: "workshop" },
  { name: "Meeting C", date: "2023-03-01", type: "meeting" }
];

// Group by month
const byMonth = groupBy(events, (event) => {
  const date = new Date(event.date);
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
});

console.log(byMonth);
// {
//   "2023-01": [
//     { name: "Meeting A", date: "2023-01-15", type: "meeting" },
//     { name: "Conference", date: "2023-01-20", type: "conference" }
//   ],
//   "2023-02": [
//     { name: "Meeting B", date: "2023-02-05", type: "meeting" },
//     { name: "Workshop", date: "2023-02-10", type: "workshop" }
//   ],
//   "2023-03": [
//     { name: "Meeting C", date: "2023-03-01", type: "meeting" }
//   ]
// }

// Group by quarter
const byQuarter = groupBy(events, (event) => {
  const date = new Date(event.date);
  const quarter = Math.ceil((date.getMonth() + 1) / 3);
  return `Q${quarter} ${date.getFullYear()}`;
});

console.log(byQuarter);
// {
//   "Q1 2023": [
//     { name: "Meeting A", date: "2023-01-15", type: "meeting" },
//     { name: "Conference", date: "2023-01-20", type: "conference" },
//     { name: "Meeting B", date: "2023-02-05", type: "meeting" },
//     { name: "Workshop", date: "2023-02-10", type: "workshop" },
//     { name: "Meeting C", date: "2023-03-01", type: "meeting" }
//   ]
// }

Data Analysis with Grouping

const sales = [
  { product: "Laptop", amount: 1200, salesperson: "Alice", region: "North" },
  { product: "Phone", amount: 800, salesperson: "Bob", region: "South" },
  { product: "Tablet", amount: 400, salesperson: "Alice", region: "North" },
  { product: "Laptop", amount: 1200, salesperson: "Charlie", region: "East" },
  { product: "Phone", amount: 800, salesperson: "Diana", region: "West" }
];

class SalesAnalyzer {
  constructor(salesData) {
    this.data = salesData;
  }

  byRegion() {
    return groupBy(this.data, 'region');
  }

  bySalesperson() {
    return groupBy(this.data, 'salesperson');
  }

  byProduct() {
    return groupBy(this.data, 'product');
  }

  getRegionTotals() {
    const grouped = this.byRegion();
    const totals = {};
    
    for (const [region, sales] of Object.entries(grouped)) {
      totals[region] = {
        totalAmount: sales.reduce((sum, sale) => sum + sale.amount, 0),
        salesCount: sales.length,
        averageAmount: sales.reduce((sum, sale) => sum + sale.amount, 0) / sales.length
      };
    }
    
    return totals;
  }

  getTopPerformers() {
    const grouped = this.bySalesperson();
    const performance = {};
    
    for (const [person, sales] of Object.entries(grouped)) {
      performance[person] = {
        totalSales: sales.reduce((sum, sale) => sum + sale.amount, 0),
        salesCount: sales.length
      };
    }
    
    return Object.entries(performance)
      .sort(([,a], [,b]) => b.totalSales - a.totalSales)
      .reduce((obj, [person, stats]) => {
        obj[person] = stats;
        return obj;
      }, {});
  }
}

const analyzer = new SalesAnalyzer(sales);

console.log("By Region:", analyzer.byRegion());
console.log("Region Totals:", analyzer.getRegionTotals());
// {
//   North: { totalAmount: 1600, salesCount: 2, averageAmount: 800 },
//   South: { totalAmount: 800, salesCount: 1, averageAmount: 800 },
//   East: { totalAmount: 1200, salesCount: 1, averageAmount: 1200 },
//   West: { totalAmount: 800, salesCount: 1, averageAmount: 800 }
// }

console.log("Top Performers:", analyzer.getTopPerformers());
// {
//   Alice: { totalSales: 1600, salesCount: 2 },
//   Charlie: { totalSales: 1200, salesCount: 1 },
//   Bob: { totalSales: 800, salesCount: 1 },
//   Diana: { totalSales: 800, salesCount: 1 }
// }

Log Analysis

const logs = [
  { timestamp: "2023-01-01T10:00:00Z", level: "INFO", message: "Server started", service: "api" },
  { timestamp: "2023-01-01T10:05:00Z", level: "ERROR", message: "Database connection failed", service: "api" },
  { timestamp: "2023-01-01T10:10:00Z", level: "WARN", message: "High memory usage", service: "worker" },
  { timestamp: "2023-01-01T10:15:00Z", level: "INFO", message: "Task completed", service: "worker" },
  { timestamp: "2023-01-01T10:20:00Z", level: "ERROR", message: "Authentication failed", service: "auth" }
];

class LogAnalyzer {
  constructor(logs) {
    this.logs = logs;
  }

  byLevel() {
    return groupBy(this.logs, 'level');
  }

  byService() {
    return groupBy(this.logs, 'service');
  }

  byHour() {
    return groupBy(this.logs, (log) => {
      const date = new Date(log.timestamp);
      return `${date.getHours()}:00`;
    });
  }

  getErrorSummary() {
    const errorLogs = this.byLevel()['ERROR'] || [];
    return groupBy(errorLogs, 'service');
  }

  getServiceHealth() {
    const byService = this.byService();
    const health = {};
    
    for (const [service, logs] of Object.entries(byService)) {
      const errorCount = logs.filter(log => log.level === 'ERROR').length;
      const warnCount = logs.filter(log => log.level === 'WARN').length;
      const totalCount = logs.length;
      
      health[service] = {
        total: totalCount,
        errors: errorCount,
        warnings: warnCount,
        errorRate: (errorCount / totalCount * 100).toFixed(2) + '%',
        status: errorCount === 0 ? 'healthy' : errorCount > totalCount * 0.5 ? 'critical' : 'warning'
      };
    }
    
    return health;
  }
}

const logAnalyzer = new LogAnalyzer(logs);

console.log("By Level:", logAnalyzer.byLevel());
console.log("Error Summary:", logAnalyzer.getErrorSummary());
// {
//   api: [
//     { timestamp: "2023-01-01T10:05:00Z", level: "ERROR", message: "Database connection failed", service: "api" }
//   ],
//   auth: [
//     { timestamp: "2023-01-01T10:20:00Z", level: "ERROR", message: "Authentication failed", service: "auth" }
//   ]
// }

console.log("Service Health:", logAnalyzer.getServiceHealth());
// {
//   api: { total: 2, errors: 1, warnings: 0, errorRate: "50.00%", status: "critical" },
//   worker: { total: 2, errors: 0, warnings: 1, errorRate: "0.00%", status: "healthy" },
//   auth: { total: 1, errors: 1, warnings: 0, errorRate: "100.00%", status: "critical" }
// }

Survey Data Processing

const surveyResponses = [
  { id: 1, age: 25, gender: "F", satisfaction: 4, department: "Engineering" },
  { id: 2, age: 32, gender: "M", satisfaction: 5, department: "Marketing" },
  { id: 3, age: 28, gender: "F", satisfaction: 3, department: "Engineering" },
  { id: 4, age: 45, gender: "M", satisfaction: 4, department: "Sales" },
  { id: 5, age: 29, gender: "F", satisfaction: 5, department: "Marketing" },
  { id: 6, age: 35, gender: "M", satisfaction: 2, department: "Engineering" }
];

class SurveyAnalyzer {
  constructor(responses) {
    this.responses = responses;
  }

  byDemographic(field) {
    return groupBy(this.responses, field);
  }

  byAgeGroup() {
    return groupBy(this.responses, (response) => {
      const age = response.age;
      if (age < 30) return "20-29";
      if (age < 40) return "30-39";
      if (age < 50) return "40-49";
      return "50+";
    });
  }

  bySatisfactionLevel() {
    return groupBy(this.responses, (response) => {
      const score = response.satisfaction;
      if (score <= 2) return "Dissatisfied";
      if (score <= 3) return "Neutral";
      if (score <= 4) return "Satisfied";
      return "Very Satisfied";
    });
  }

  getDepartmentSatisfaction() {
    const byDept = this.byDemographic('department');
    const satisfaction = {};
    
    for (const [dept, responses] of Object.entries(byDept)) {
      const scores = responses.map(r => r.satisfaction);
      const average = scores.reduce((sum, score) => sum + score, 0) / scores.length;
      
      satisfaction[dept] = {
        averageScore: Math.round(average * 100) / 100,
        responseCount: responses.length,
        distribution: this.getScoreDistribution(scores)
      };
    }
    
    return satisfaction;
  }

  getScoreDistribution(scores) {
    const distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
    scores.forEach(score => distribution[score]++);
    return distribution;
  }

  getCrossTabulation(field1, field2) {
    const result = {};
    
    this.responses.forEach(response => {
      const key1 = response[field1];
      const key2 = response[field2];
      
      if (!result[key1]) result[key1] = {};
      if (!result[key1][key2]) result[key1][key2] = 0;
      
      result[key1][key2]++;
    });
    
    return result;
  }
}

const surveyAnalyzer = new SurveyAnalyzer(surveyResponses);

console.log("By Age Group:", surveyAnalyzer.byAgeGroup());
console.log("By Satisfaction:", surveyAnalyzer.bySatisfactionLevel());
console.log("Department Satisfaction:", surveyAnalyzer.getDepartmentSatisfaction());
// {
//   Engineering: { 
//     averageScore: 3.33, 
//     responseCount: 3, 
//     distribution: { 1: 0, 2: 1, 3: 1, 4: 1, 5: 0 } 
//   },
//   Marketing: { 
//     averageScore: 5, 
//     responseCount: 2, 
//     distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 2 } 
//   },
//   Sales: { 
//     averageScore: 4, 
//     responseCount: 1, 
//     distribution: { 1: 0, 2: 0, 3: 0, 4: 1, 5: 0 } 
//   }
// }

console.log("Gender vs Department:", surveyAnalyzer.getCrossTabulation('gender', 'department'));
// {
//   F: { Engineering: 2, Marketing: 1 },
//   M: { Marketing: 1, Sales: 1, Engineering: 1 }
// }