Lesson 5
Data Manipulation Techniques with TypeScript
Topic Overview and Importance

Hello and welcome! Today, we're exploring practical data manipulation techniques in TypeScript. We'll utilize TypeScript arrays to represent our data streams and perform projection, filtering, and aggregation operations. Our operations will be neatly encapsulated within a TypeScript class for clean, structured code with built-in type annotations.

Introduction to Data Manipulation

Data manipulation is akin to sculpting, but for data. We shape our dataset to derive the desired structure. TypeScript arrays with type annotations give us clarity and safety. Our manipulation operations will be part of a TypeScript class. Let's prepare our toolbox:

TypeScript
1type DataElement = { 2 name: string; 3 age: number; 4 profession: string; 5 salary: number 6}; 7 8class DataStream { 9 data: DataElement[]; 10 11 constructor(data: DataElement[]) { 12 this.data = data; 13 } 14}
Data Projection in Practice

Our first stop is data projection, akin to capturing a photo of our desired features. Imagine we have data about people. If we're only interested in names and ages, we can project our data to focus solely on these details. We'll extend our DataStream class with a projectData method, leveraging TypeScript's type system:

TypeScript
1projectData(keys: string[]): DataStream { 2 const projectedData = this.data.map((d) => { 3 const newObj: Partial<DataElement> = {}; 4 keys.forEach((key) => { 5 newObj[key] = d[key]; 6 }); 7 return newObj; 8 }); 9 return new DataStream(projectedData as DataElement[]); 10}

The projectData method in the DataStream class creates a new DataStream instance that contains only the specified keys from each element in the original data array. It accepts an array of keys as input. The method iterates over each element in the data and constructs a new object, newObj, for each element. Using Partial<DataElement> for newObj allows these objects to only include some properties of DataElement, making them optional. In the method, the forEach loop assigns the value of each specified key to newObj, ensuring that only desired fields are retained. The newly constructed projectedData, which consists of partially constructed DataElement objects, is then used to instantiate and return a new DataStream.

Data Filtering in Practice

Next, we practice data filtering, which is akin to cherry-picking preferred data entries. We'll extend our DataStream class with a filterData method that uses a "test" function to filter data:

TypeScript
1filterData(testFunc: (element: DataElement) => boolean): DataStream { 2 const filteredData = this.data.filter(testFunc); 3 return new DataStream(filteredData); 4}

The filterData method in the DataStream class generates a new DataStream instance with elements that meet a specified condition, determined by the testFunc function. This method accepts testFunc as a parameter, which returns a boolean to indicate whether each element should be included in the filtered dataset. It then uses the filter method, applying the condition to each element in the data array, and creates a new DataStream instance with the elements that pass the condition.

Data Aggregation in Practice

Finally, we come to data aggregation, where we condense data into a summary. We'll add an aggregateData method to our DataStream class, utilizing TypeScript's strong typing for the aggregation function:

TypeScript
1aggregateData(key: string, aggFunc: (values: number[]) => number): number { 2 const values = this.data.map((d) => d[key as string] as number); 3 return aggFunc(values); 4}

The aggregateData method in the DataStream class is designed to create a summary statistic of a particular field within the data elements. It takes a key string and a custom aggFunc aggregation function as parameters. The method iterates over the data array and extracts the values associated with the provided key, using d[key as string] as number to ensure that the resulting values are treated as numbers. These values are then passed to the aggFunc, which processes the array of numbers to produce a single numerical result. This method allows for versatile aggregation operations, such as calculating averages, sums, or any other custom aggregation defined by the input function.

Combining Projection, Filtering, and Aggregation

Now, let's combine projection, filtering, and aggregation to witness the power of these techniques. We adjust our example to demonstrate this flow, ensuring type safety using TypeScript annotations throughout:

  1. Data Projection: Choose only the desired fields.
  2. Data Filtering: Filter based on specified conditions.
  3. Data Aggregation: Summarize the filtered data.
TypeScript
1type DataElement = { 2 name: string; 3 age: number; 4 profession: string; 5 salary: number 6}; 7 8class DataStream { 9 data: DataElement[]; 10 11 constructor(data: DataElement[]) { 12 this.data = data; 13 } 14 15 projectData(keys: string[]): DataStream { 16 const projectedData = this.data.map((d) => { 17 const newObj: Partial<DataElement> = {}; 18 keys.forEach((key) => { 19 newObj[key] = d[key]; 20 }); 21 return newObj; 22 }); 23 return new DataStream(projectedData as DataElement[]); 24 } 25 26 filterData(testFunc: (element: DataElement) => boolean): DataStream { 27 const filteredData = this.data.filter(testFunc); 28 return new DataStream(filteredData); 29 } 30 31 aggregateData(key: string, aggFunc: (values: number[]) => number): number { 32 const values = this.data.map((d) => d[key as string] as number); 33 return aggFunc(values); 34 } 35} 36 37// Example usage 38const ds = new DataStream([ 39 { name: 'Alice', age: 25, profession: 'Engineer', salary: 70000 }, 40 { name: 'Bob', age: 30, profession: 'Doctor', salary: 120000 }, 41 { name: 'Carol', age: 35, profession: 'Artist', salary: 50000 }, 42 { name: 'David', age: 40, profession: 'Engineer', salary: 90000 }, 43]); 44 45const projectedDs = ds.projectData(['name', 'age', 'salary']); 46const filteredDs = projectedDs.filterData((x: DataElement) => x.age > 30); 47const averageSalary = filteredDs.aggregateData('salary', (salaries) => salaries.reduce((a, b) => a + b, 0) / salaries.length); 48console.log(averageSalary); // Outputs: 70000.0

Here:

  • Projection: We focus on name, age, and salary fields. projectData returns a DataStream, allowing operation chaining.
  • Filtering: We filter individuals aged over 30, and filterData returns a DataStream.
  • Aggregation: We calculate the average salary for filtered data, demonstrating TypeScript's power and conciseness.
Lesson Summary

Excellent work! You've now mastered data projection, filtering, and aggregation with TypeScript arrays. By leveraging type annotations, you achieve clear and safe code. You've also learned to encapsulate these operations in a TypeScript class, creating efficient, reusable code structures. Now, continue your practice with exercises that challenge your newfound skills. Ready for more data fun? Let's dive in!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.